@ -12,7 +12,7 @@
*
* The plan cache manager itself is principally responsible for tracking
* whether cached plans should be invalidated because of schema changes in
* the table s they depend on . When ( and if ) the next demand for a cached
* the object s they depend on . When ( and if ) the next demand for a cached
* plan occurs , the query will be replanned . Note that this could result
* in an error , for example if a column referenced by the query is no
* longer present . The creator of a cached plan can specify whether it
@ -20,20 +20,22 @@
* could happen with " SELECT * " for example ) - - - if so , it ' s up to the
* caller to notice changes and cope with them .
*
* Currently , we use only relcache invalidation events to invalidate plans .
* This means that changes such as modification of a function definition do
* not invalidate plans using the function . This is not 100 % OK - - - for
* example , changing a SQL function that ' s been inlined really ought to
* cause invalidation of the plan that it ' s been inlined into - - - but the
* cost of tracking additional types of object seems much higher than the
* gain , so we ' re just ignoring them for now .
* Currently , we track exactly the dependencies of plans on relations and
* user - defined functions . On relcache invalidation events or pg_proc
* syscache invalidation events , we invalidate just those plans that depend
* on the particular object being modified . ( Note : this scheme assumes
* that any table modification that requires replanning will generate a
* relcache inval event . ) We also watch for inval events on certain other
* system catalogs , such as pg_namespace ; but for them , our response is
* just to invalidate all plans . We expect updates on those catalogs to
* be infrequent enough that more - detailed tracking is not worth the effort .
*
*
* Portions Copyright ( c ) 1996 - 2008 , PostgreSQL Global Development Group
* Portions Copyright ( c ) 1994 , Regents of the University of California
*
* IDENTIFICATION
* $ PostgreSQL : pgsql / src / backend / utils / cache / plancache . c , v 1.20 2008 / 08 / 25 22 : 42 : 34 tgl Exp $
* $ PostgreSQL : pgsql / src / backend / utils / cache / plancache . c , v 1.21 2008 / 09 / 09 18 : 58 : 08 tgl Exp $
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
@ -44,6 +46,7 @@
# include "catalog/namespace.h"
# include "executor/executor.h"
# include "nodes/nodeFuncs.h"
# include "optimizer/planmain.h"
# include "storage/lmgr.h"
# include "tcop/pquery.h"
# include "tcop/tcopprot.h"
@ -52,19 +55,7 @@
# include "utils/memutils.h"
# include "utils/resowner.h"
# include "utils/snapmgr.h"
typedef struct
{
void ( * callback ) ( ) ;
void * arg ;
} ScanQueryWalkerContext ;
typedef struct
{
Oid inval_relid ;
CachedPlan * plan ;
} InvalRelidContext ;
# include "utils/syscache.h"
static List * cached_plans_list = NIL ;
@ -73,28 +64,28 @@ static void StoreCachedPlan(CachedPlanSource *plansource, List *stmt_list,
MemoryContext plan_context ) ;
static void AcquireExecutorLocks ( List * stmt_list , bool acquire ) ;
static void AcquirePlannerLocks ( List * stmt_list , bool acquire ) ;
static void LockRelid ( Oid relid , LOCKMODE lockmode , void * arg ) ;
static void UnlockRelid ( Oid relid , LOCKMODE lockmode , void * arg ) ;
static void ScanQueryForRelids ( Query * parsetree ,
void ( * callback ) ( ) ,
void * arg ) ;
static bool ScanQueryWalker ( Node * node , ScanQueryWalkerContext * context ) ;
static void ScanQueryForLocks ( Query * parsetree , bool acquire ) ;
static bool ScanQueryWalker ( Node * node , bool * acquire ) ;
static bool rowmark_member ( List * rowMarks , int rt_index ) ;
static bool plan_list_is_transient ( List * stmt_list ) ;
static void PlanCacheCallback ( Datum arg , Oid relid ) ;
static void InvalRelid ( Oid relid , LOCKMODE lockmode ,
InvalRelidContext * context ) ;
static void PlanCacheRelCallback ( Datum arg , Oid relid ) ;
static void PlanCacheFuncCallback ( Datum arg , int cacheid , ItemPointer tuplePtr ) ;
static void PlanCacheSysCallback ( Datum arg , int cacheid , ItemPointer tuplePtr ) ;
/*
* InitPlanCache : initialize module during InitPostgres .
*
* All we need to do is hook into inval . c ' s callback list .
* All we need to do is hook into inval . c ' s callback lists .
*/
void
InitPlanCache ( void )
{
CacheRegisterRelcacheCallback ( PlanCacheCallback , ( Datum ) 0 ) ;
CacheRegisterRelcacheCallback ( PlanCacheRelCallback , ( Datum ) 0 ) ;
CacheRegisterSyscacheCallback ( PROCOID , PlanCacheFuncCallback , ( Datum ) 0 ) ;
CacheRegisterSyscacheCallback ( NAMESPACEOID , PlanCacheSysCallback , ( Datum ) 0 ) ;
CacheRegisterSyscacheCallback ( OPEROID , PlanCacheSysCallback , ( Datum ) 0 ) ;
CacheRegisterSyscacheCallback ( AMOPOPID , PlanCacheSysCallback , ( Datum ) 0 ) ;
}
/*
@ -337,6 +328,18 @@ StoreCachedPlan(CachedPlanSource *plansource,
plan - > refcount = 1 ; /* for the parent's link */
plan - > generation = + + ( plansource - > generation ) ;
plan - > context = plan_context ;
if ( plansource - > fully_planned )
{
/* Planner already extracted dependencies, we don't have to */
plan - > relationOids = plan - > invalItems = NIL ;
}
else
{
/* Use the planner machinery to extract dependencies */
extract_query_dependencies ( stmt_list ,
& plan - > relationOids ,
& plan - > invalItems ) ;
}
Assert ( plansource - > plan = = NULL ) ;
plansource - > plan = plan ;
@ -432,8 +435,8 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
plan - > dead = true ;
/*
* By now , if any invalidation has happened , PlanCacheCallback will
* have marked the plan dead .
* By now , if any invalidation has happened , the inval callback
* functions will have marked the plan dead .
*/
if ( plan - > dead )
{
@ -637,37 +640,15 @@ AcquirePlannerLocks(List *stmt_list, bool acquire)
Query * query = ( Query * ) lfirst ( lc ) ;
Assert ( IsA ( query , Query ) ) ;
if ( acquire )
ScanQueryForRelids ( query , LockRelid , NULL ) ;
else
ScanQueryForRelids ( query , UnlockRelid , NULL ) ;
ScanQueryForLocks ( query , acquire ) ;
}
}
/*
* ScanQueryForRelids callback functions for AcquirePlannerLocks
*/
static void
LockRelid ( Oid relid , LOCKMODE lockmode , void * arg )
{
LockRelationOid ( relid , lockmode ) ;
}
static void
UnlockRelid ( Oid relid , LOCKMODE lockmode , void * arg )
{
UnlockRelationOid ( relid , lockmode ) ;
}
/*
* ScanQueryForRelids : recursively scan one Query and apply the callback
* function to each relation OID found therein . The callback function
* takes the arguments relation OID , lockmode , pointer arg .
* ScanQueryForLocks : recursively scan one Query for AcquirePlannerLocks .
*/
static void
ScanQueryForRelids ( Query * parsetree ,
void ( * callback ) ( ) ,
void * arg )
ScanQueryForLocks ( Query * parsetree , bool acquire )
{
ListCell * lc ;
int rt_index ;
@ -685,27 +666,22 @@ ScanQueryForRelids(Query *parsetree,
switch ( rte - > rtekind )
{
case RTE_RELATION :
/*
* Determine the lock type required for this RTE .
*/
/* Acquire or release the appropriate type of lock */
if ( rt_index = = parsetree - > resultRelation )
lockmode = RowExclusiveLock ;
else if ( rowmark_member ( parsetree - > rowMarks , rt_index ) )
lockmode = RowShareLock ;
else
lockmode = AccessShareLock ;
( * callback ) ( rte - > relid , lockmode , arg ) ;
if ( acquire )
LockRelationOid ( rte - > relid , lockmode ) ;
else
UnlockRelationOid ( rte - > relid , lockmode ) ;
break ;
case RTE_SUBQUERY :
/*
* The subquery RTE itself is all right , but we have to
* recurse to process the represented subquery .
*/
ScanQueryForRelids ( rte - > subquery , callback , arg ) ;
/* Recurse into subquery-in-FROM */
ScanQueryForLocks ( rte - > subquery , acquire ) ;
break ;
default :
@ -720,21 +696,17 @@ ScanQueryForRelids(Query *parsetree,
*/
if ( parsetree - > hasSubLinks )
{
ScanQueryWalkerContext context ;
context . callback = callback ;
context . arg = arg ;
query_tree_walker ( parsetree , ScanQueryWalker ,
( void * ) & context ,
( void * ) & acquire ,
QTW_IGNORE_RT_SUBQUERIES ) ;
}
}
/*
* Walker to find sublink subqueries for ScanQueryForRelid s
* Walker to find sublink subqueries for ScanQueryForLock s
*/
static bool
ScanQueryWalker ( Node * node , ScanQueryWalkerContext * context )
ScanQueryWalker ( Node * node , bool * acquire )
{
if ( node = = NULL )
return false ;
@ -743,17 +715,16 @@ ScanQueryWalker(Node *node, ScanQueryWalkerContext *context)
SubLink * sub = ( SubLink * ) node ;
/* Do what we came for */
ScanQueryForRelids ( ( Query * ) sub - > subselect ,
context - > callback , context - > arg ) ;
ScanQueryForLocks ( ( Query * ) sub - > subselect , * acquire ) ;
/* Fall through to process lefthand args of SubLink */
}
/*
* Do NOT recurse into Query nodes , because ScanQueryForRelid s already
* Do NOT recurse into Query nodes , because ScanQueryForLock s already
* processed subselects of subselects for us .
*/
return expression_tree_walker ( node , ScanQueryWalker ,
( void * ) context ) ;
( void * ) acquire ) ;
}
/*
@ -863,17 +834,16 @@ PlanCacheComputeResultDesc(List *stmt_list)
}
/*
* PlanCacheCallback
* PlanCacheRel Callback
* Relcache inval callback function
*
* Invalidate all plans mentioning the given rel , or all plans mentioning
* any rel at all if relid = = InvalidOid .
*/
static void
PlanCacheCallback ( Datum arg , Oid relid )
PlanCacheRel Callback ( Datum arg , Oid relid )
{
ListCell * lc1 ;
ListCell * lc2 ;
foreach ( lc1 , cached_plans_list )
{
@ -885,6 +855,9 @@ PlanCacheCallback(Datum arg, Oid relid)
continue ;
if ( plan - > fully_planned )
{
/* Have to check the per-PlannedStmt relid lists */
ListCell * lc2 ;
foreach ( lc2 , plan - > stmt_list )
{
PlannedStmt * plannedstmt = ( PlannedStmt * ) lfirst ( lc2 ) ;
@ -903,42 +876,117 @@ PlanCacheCallback(Datum arg, Oid relid)
}
else
{
/*
* For not - fully - planned entries we use ScanQueryForRelids , since
* a recursive traversal is needed . The callback API is a bit
* tedious but avoids duplication of coding .
*/
InvalRelidContext context ;
/* Otherwise check the single list we built ourselves */
if ( ( relid = = InvalidOid ) ? plan - > relationOids ! = NIL :
list_member_oid ( plan - > relationOids , relid ) )
plan - > dead = true ;
}
}
}
context . inval_relid = relid ;
context . plan = plan ;
/*
* PlanCacheFuncCallback
* Syscache inval callback function for PROCOID cache
*
* Invalidate all plans mentioning the given catalog entry , or all plans
* mentioning any member of this cache if tuplePtr = = NULL .
*
* Note that the coding would support use for multiple caches , but right
* now only user - defined functions are tracked this way .
*/
static void
PlanCacheFuncCallback ( Datum arg , int cacheid , ItemPointer tuplePtr )
{
ListCell * lc1 ;
foreach ( lc1 , cached_plans_list )
{
CachedPlanSource * plansource = ( CachedPlanSource * ) lfirst ( lc1 ) ;
CachedPlan * plan = plansource - > plan ;
/* No work if it's already invalidated */
if ( ! plan | | plan - > dead )
continue ;
if ( plan - > fully_planned )
{
/* Have to check the per-PlannedStmt inval-item lists */
ListCell * lc2 ;
foreach ( lc2 , plan - > stmt_list )
{
Query * query = ( Query * ) lfirst ( lc2 ) ;
PlannedStmt * plannedstmt = ( PlannedStmt * ) lfirst ( lc2 ) ;
ListCell * lc3 ;
Assert ( ! IsA ( plannedstmt , Query ) ) ;
if ( ! IsA ( plannedstmt , PlannedStmt ) )
continue ; /* Ignore utility statements */
foreach ( lc3 , plannedstmt - > invalItems )
{
PlanInvalItem * item = ( PlanInvalItem * ) lfirst ( lc3 ) ;
if ( item - > cacheId ! = cacheid )
continue ;
if ( tuplePtr = = NULL | |
ItemPointerEquals ( tuplePtr , & item - > tupleId ) )
{
/* Invalidate the plan! */
plan - > dead = true ;
break ; /* out of invalItems scan */
}
}
if ( plan - > dead )
break ; /* out of stmt_list scan */
}
}
else
{
/* Otherwise check the single list we built ourselves */
ListCell * lc2 ;
foreach ( lc2 , plan - > invalItems )
{
PlanInvalItem * item = ( PlanInvalItem * ) lfirst ( lc2 ) ;
Assert ( IsA ( query , Query ) ) ;
ScanQueryForRelids ( query , InvalRelid , ( void * ) & context ) ;
if ( item - > cacheId ! = cacheid )
continue ;
if ( tuplePtr = = NULL | |
ItemPointerEquals ( tuplePtr , & item - > tupleId ) )
{
/* Invalidate the plan! */
plan - > dead = true ;
break ;
}
}
}
}
}
/*
* ResetPlanCache : drop all cached plans .
* PlanCacheSysCallback
* Syscache inval callback function for other caches
*
* Just invalidate everything . . .
*/
void
ResetPlanCache ( void )
static void
PlanCacheSysCallback ( Datum arg , int cacheid , ItemPointer tuplePtr )
{
PlanCacheCallback ( ( Datum ) 0 , InvalidOid ) ;
Reset PlanCache( ) ;
}
/*
* ScanQueryForRelids callback function for PlanCacheCallback
* ResetPlanCache : drop all cached plans .
*/
static void
InvalRelid ( Oid relid , LOCKMODE lockmode , InvalRelidContext * context )
void
ResetPlanCache ( void )
{
if ( relid = = context - > inval_relid | | context - > inval_relid = = InvalidOid )
context - > plan - > dead = true ;
ListCell * lc ;
foreach ( lc , cached_plans_list )
{
CachedPlanSource * plansource = ( CachedPlanSource * ) lfirst ( lc ) ;
CachedPlan * plan = plansource - > plan ;
if ( plan )
plan - > dead = true ;
}
}