* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.111 2008/09/01 20:42:43 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.112 2008/09/09 18:58:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static void InitTempTableNamespace(void);
static void RemoveTempRelations(Oid tempNamespaceId);
static void RemoveTempRelationsCallback(int code, Datum arg);
-static void NamespaceCallback(Datum arg, Oid relid);
+static void NamespaceCallback(Datum arg, int cacheid, ItemPointer tuplePtr);
/* These don't really need to appear in any header file */
Datum pg_table_is_visible(PG_FUNCTION_ARGS);
* Syscache inval callback function
*/
static void
-NamespaceCallback(Datum arg, Oid relid)
+NamespaceCallback(Datum arg, int cacheid, ItemPointer tuplePtr)
{
/* Force search path to be recomputed on next use */
baseSearchPathValid = false;
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.404 2008/09/01 20:42:44 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.405 2008/09/09 18:58:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(rowMarks);
COPY_NODE_FIELD(relationOids);
+ COPY_NODE_FIELD(invalItems);
COPY_SCALAR_FIELD(nParamExec);
return newnode;
return newnode;
}
+/*
+ * _copyPlanInvalItem
+ */
+static PlanInvalItem *
+_copyPlanInvalItem(PlanInvalItem *from)
+{
+ PlanInvalItem *newnode = makeNode(PlanInvalItem);
+
+ COPY_SCALAR_FIELD(cacheId);
+ /* tupleId isn't really a "scalar", but this works anyway */
+ COPY_SCALAR_FIELD(tupleId);
+
+ return newnode;
+}
+
/* ****************************************************************
* primnodes.h copy functions
* ****************************************************************
case T_Limit:
retval = _copyLimit(from);
break;
+ case T_PlanInvalItem:
+ retval = _copyPlanInvalItem(from);
+ break;
/*
* PRIMITIVE NODES
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.338 2008/09/01 20:42:44 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.339 2008/09/09 18:58:08 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(relationOids);
+ WRITE_NODE_FIELD(invalItems);
WRITE_INT_FIELD(nParamExec);
}
appendStringInfo(str, " %u", node->uniqOperators[i]);
}
+static void
+_outHash(StringInfo str, Hash *node)
+{
+ WRITE_NODE_TYPE("HASH");
+
+ _outPlanInfo(str, (Plan *) node);
+}
+
static void
_outSetOp(StringInfo str, SetOp *node)
{
}
static void
-_outHash(StringInfo str, Hash *node)
+_outPlanInvalItem(StringInfo str, PlanInvalItem *node)
{
- WRITE_NODE_TYPE("HASH");
+ WRITE_NODE_TYPE("PLANINVALITEM");
- _outPlanInfo(str, (Plan *) node);
+ WRITE_INT_FIELD(cacheId);
+ appendStringInfo(str, " :tupleId (%u,%u)",
+ ItemPointerGetBlockNumber(&node->tupleId),
+ ItemPointerGetOffsetNumber(&node->tupleId));
}
/*****************************************************************************
WRITE_BITMAPSET_FIELD(rewindPlanIDs);
WRITE_NODE_FIELD(finalrtable);
WRITE_NODE_FIELD(relationOids);
+ WRITE_NODE_FIELD(invalItems);
}
static void
case T_Unique:
_outUnique(str, obj);
break;
+ case T_Hash:
+ _outHash(str, obj);
+ break;
case T_SetOp:
_outSetOp(str, obj);
break;
case T_Limit:
_outLimit(str, obj);
break;
- case T_Hash:
- _outHash(str, obj);
+ case T_PlanInvalItem:
+ _outPlanInvalItem(str, obj);
break;
case T_Alias:
_outAlias(str, obj);
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.242 2008/08/17 01:19:59 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.243 2008/09/09 18:58:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
glob->rewindPlanIDs = NULL;
glob->finalrtable = NIL;
glob->relationOids = NIL;
+ glob->invalItems = NIL;
glob->transientPlan = false;
/* Determine what fraction of the plan is likely to be scanned */
result->returningLists = root->returningLists;
result->rowMarks = parse->rowMarks;
result->relationOids = glob->relationOids;
+ result->invalItems = glob->invalItems;
result->nParamExec = list_length(glob->paramlist);
return result;
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.143 2008/08/25 22:42:33 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.144 2008/09/09 18:58:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
+#include "access/transam.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/tlist.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
+#include "utils/syscache.h"
typedef struct
static Node *fix_upper_expr_mutator(Node *node,
fix_upper_expr_context *context);
static bool fix_opfuncids_walker(Node *node, void *context);
+static bool extract_query_dependencies_walker(Node *node,
+ PlannerGlobal *context);
/*****************************************************************************
* 4. We compute regproc OIDs for operators (ie, we look up the function
* that implements each op).
*
- * 5. We create a list of OIDs of relations that the plan depends on.
+ * 5. We create lists of specific objects that the plan depends on.
* This will be used by plancache.c to drive invalidation of cached plans.
- * (Someday we might want to generalize this to include other types of
- * objects, but for now tracking relations seems to solve most problems.)
+ * Relation dependencies are represented by OIDs, and everything else by
+ * PlanInvalItems (this distinction is motivated by the shared-inval APIs).
+ * Currently, relations and user-defined functions are the only types of
+ * objects that are explicitly tracked this way.
*
* We also perform one final optimization step, which is to delete
* SubqueryScan plan nodes that aren't doing anything useful (ie, have
* different when the passed-in Plan is a SubqueryScan we decide isn't needed.
*
* The flattened rangetable entries are appended to glob->finalrtable, and
- * the list of relation OIDs is appended to glob->relationOids.
+ * plan dependencies are appended to glob->relationOids (for relations)
+ * and glob->invalItems (for everything else).
*
* Notice that we modify Plan nodes in-place, but use expression_tree_mutator
* to process targetlist and qual expressions. We can assume that the Plan
return newvar;
}
+/*
+ * fix_expr_common
+ * Do generic set_plan_references processing on an expression node
+ *
+ * This is code that is common to all variants of expression-fixing.
+ * We must look up operator opcode info for OpExpr and related nodes,
+ * add OIDs from regclass Const nodes into glob->relationOids,
+ * and add catalog TIDs for user-defined functions into glob->invalItems.
+ *
+ * We assume it's okay to update opcode info in-place. So this could possibly
+ * scribble on the planner's input data structures, but it's OK.
+ */
+static void
+fix_expr_common(PlannerGlobal *glob, Node *node)
+{
+ /* We assume callers won't call us on a NULL pointer */
+ if (IsA(node, Aggref))
+ {
+ record_plan_function_dependency(glob,
+ ((Aggref *) node)->aggfnoid);
+ }
+ else if (IsA(node, FuncExpr))
+ {
+ record_plan_function_dependency(glob,
+ ((FuncExpr *) node)->funcid);
+ }
+ else if (IsA(node, OpExpr))
+ {
+ set_opfuncid((OpExpr *) node);
+ record_plan_function_dependency(glob,
+ ((OpExpr *) node)->opfuncid);
+ }
+ else if (IsA(node, DistinctExpr))
+ {
+ set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
+ record_plan_function_dependency(glob,
+ ((DistinctExpr *) node)->opfuncid);
+ }
+ else if (IsA(node, NullIfExpr))
+ {
+ set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
+ record_plan_function_dependency(glob,
+ ((NullIfExpr *) node)->opfuncid);
+ }
+ else if (IsA(node, ScalarArrayOpExpr))
+ {
+ set_sa_opfuncid((ScalarArrayOpExpr *) node);
+ record_plan_function_dependency(glob,
+ ((ScalarArrayOpExpr *) node)->opfuncid);
+ }
+ else if (IsA(node, ArrayCoerceExpr))
+ {
+ if (OidIsValid(((ArrayCoerceExpr *) node)->elemfuncid))
+ record_plan_function_dependency(glob,
+ ((ArrayCoerceExpr *) node)->elemfuncid);
+ }
+ else if (IsA(node, Const))
+ {
+ Const *con = (Const *) node;
+
+ /* Check for regclass reference */
+ if (ISREGCLASSCONST(con))
+ glob->relationOids =
+ lappend_oid(glob->relationOids,
+ DatumGetObjectId(con->constvalue));
+ }
+}
+
/*
* fix_scan_expr
* Do set_plan_references processing on a scan-level expression
cexpr->cvarno += context->rtoffset;
return (Node *) cexpr;
}
-
- /*
- * Since we update opcode info in-place, this part could possibly scribble
- * on the planner's input data structures, but it's OK.
- */
- if (IsA(node, OpExpr))
- set_opfuncid((OpExpr *) node);
- else if (IsA(node, DistinctExpr))
- set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
- else if (IsA(node, NullIfExpr))
- set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
- else if (IsA(node, ScalarArrayOpExpr))
- set_sa_opfuncid((ScalarArrayOpExpr *) node);
- else if (IsA(node, Const))
- {
- Const *con = (Const *) node;
-
- /* Check for regclass reference */
- if (ISREGCLASSCONST(con))
- context->glob->relationOids =
- lappend_oid(context->glob->relationOids,
- DatumGetObjectId(con->constvalue));
- /* Fall through to let expression_tree_mutator copy it */
- }
+ fix_expr_common(context->glob, node);
return expression_tree_mutator(node, fix_scan_expr_mutator,
(void *) context);
}
{
if (node == NULL)
return false;
- if (IsA(node, OpExpr))
- set_opfuncid((OpExpr *) node);
- else if (IsA(node, DistinctExpr))
- set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
- else if (IsA(node, NullIfExpr))
- set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
- else if (IsA(node, ScalarArrayOpExpr))
- set_sa_opfuncid((ScalarArrayOpExpr *) node);
- else if (IsA(node, Const))
- {
- Const *con = (Const *) node;
-
- /* Check for regclass reference */
- if (ISREGCLASSCONST(con))
- context->glob->relationOids =
- lappend_oid(context->glob->relationOids,
- DatumGetObjectId(con->constvalue));
- return false;
- }
+ fix_expr_common(context->glob, node);
return expression_tree_walker(node, fix_scan_expr_walker,
(void *) context);
}
if (newvar)
return (Node *) newvar;
}
-
- /*
- * Since we update opcode info in-place, this part could possibly scribble
- * on the planner's input data structures, but it's OK.
- */
- if (IsA(node, OpExpr))
- set_opfuncid((OpExpr *) node);
- else if (IsA(node, DistinctExpr))
- set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
- else if (IsA(node, NullIfExpr))
- set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
- else if (IsA(node, ScalarArrayOpExpr))
- set_sa_opfuncid((ScalarArrayOpExpr *) node);
- else if (IsA(node, Const))
- {
- Const *con = (Const *) node;
-
- /* Check for regclass reference */
- if (ISREGCLASSCONST(con))
- context->glob->relationOids =
- lappend_oid(context->glob->relationOids,
- DatumGetObjectId(con->constvalue));
- /* Fall through to let expression_tree_mutator copy it */
- }
+ fix_expr_common(context->glob, node);
return expression_tree_mutator(node,
fix_join_expr_mutator,
(void *) context);
if (newvar)
return (Node *) newvar;
}
-
- /*
- * Since we update opcode info in-place, this part could possibly scribble
- * on the planner's input data structures, but it's OK.
- */
- if (IsA(node, OpExpr))
- set_opfuncid((OpExpr *) node);
- else if (IsA(node, DistinctExpr))
- set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
- else if (IsA(node, NullIfExpr))
- set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
- else if (IsA(node, ScalarArrayOpExpr))
- set_sa_opfuncid((ScalarArrayOpExpr *) node);
- else if (IsA(node, Const))
- {
- Const *con = (Const *) node;
-
- /* Check for regclass reference */
- if (ISREGCLASSCONST(con))
- context->glob->relationOids =
- lappend_oid(context->glob->relationOids,
- DatumGetObjectId(con->constvalue));
- /* Fall through to let expression_tree_mutator copy it */
- }
+ fix_expr_common(context->glob, node);
return expression_tree_mutator(node,
fix_upper_expr_mutator,
(void *) context);
if (opexpr->opfuncid == InvalidOid)
opexpr->opfuncid = get_opcode(opexpr->opno);
}
+
+/*****************************************************************************
+ * QUERY DEPENDENCY MANAGEMENT
+ *****************************************************************************/
+
+/*
+ * record_plan_function_dependency
+ * Mark the current plan as depending on a particular function.
+ *
+ * This is exported so that the function-inlining code can record a
+ * dependency on a function that it's removed from the plan tree.
+ */
+void
+record_plan_function_dependency(PlannerGlobal *glob, Oid funcid)
+{
+ /*
+ * For performance reasons, we don't bother to track built-in functions;
+ * we just assume they'll never change (or at least not in ways that'd
+ * invalidate plans using them). For this purpose we can consider a
+ * built-in function to be one with OID less than FirstBootstrapObjectId.
+ * Note that the OID generator guarantees never to generate such an
+ * OID after startup, even at OID wraparound.
+ */
+ if (funcid >= (Oid) FirstBootstrapObjectId)
+ {
+ HeapTuple func_tuple;
+ PlanInvalItem *inval_item;
+
+ func_tuple = SearchSysCache(PROCOID,
+ ObjectIdGetDatum(funcid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(func_tuple))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+
+ inval_item = makeNode(PlanInvalItem);
+
+ /*
+ * It would work to use any syscache on pg_proc, but plancache.c
+ * expects us to use PROCOID.
+ */
+ inval_item->cacheId = PROCOID;
+ inval_item->tupleId = func_tuple->t_self;
+
+ glob->invalItems = lappend(glob->invalItems, inval_item);
+
+ ReleaseSysCache(func_tuple);
+ }
+}
+
+/*
+ * extract_query_dependencies
+ * Given a list of not-yet-planned queries (i.e. Query nodes),
+ * extract their dependencies just as set_plan_references would do.
+ *
+ * This is needed by plancache.c to handle invalidation of cached unplanned
+ * queries.
+ */
+void
+extract_query_dependencies(List *queries,
+ List **relationOids,
+ List **invalItems)
+{
+ PlannerGlobal glob;
+
+ /* Make up a dummy PlannerGlobal so we can use this module's machinery */
+ MemSet(&glob, 0, sizeof(glob));
+ glob.type = T_PlannerGlobal;
+ glob.relationOids = NIL;
+ glob.invalItems = NIL;
+
+ (void) extract_query_dependencies_walker((Node *) queries, &glob);
+
+ *relationOids = glob.relationOids;
+ *invalItems = glob.invalItems;
+}
+
+static bool
+extract_query_dependencies_walker(Node *node, PlannerGlobal *context)
+{
+ if (node == NULL)
+ return false;
+ /* Extract function dependencies and check for regclass Consts */
+ fix_expr_common(context, node);
+ if (IsA(node, Query))
+ {
+ Query *query = (Query *) node;
+ ListCell *lc;
+
+ /* Collect relation OIDs in this Query's rtable */
+ foreach(lc, query->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+ if (rte->rtekind == RTE_RELATION)
+ context->relationOids = lappend_oid(context->relationOids,
+ rte->relid);
+ }
+
+ /* And recurse into the query's subexpressions */
+ return query_tree_walker(query, extract_query_dependencies_walker,
+ (void *) context, 0);
+ }
+ return expression_tree_walker(node, extract_query_dependencies_walker,
+ (void *) context);
+}
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.266 2008/08/28 23:09:46 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.267 2008/09/09 18:58:08 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
typedef struct
{
ParamListInfo boundParams;
+ PlannerGlobal *glob;
List *active_fns;
Node *case_val;
bool estimate;
eval_const_expressions_context context;
if (root)
+ {
context.boundParams = root->glob->boundParams; /* bound Params */
+ context.glob = root->glob; /* for inlined-function dependencies */
+ }
else
+ {
context.boundParams = NULL;
+ context.glob = NULL;
+ }
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = false; /* safe transformations only */
eval_const_expressions_context context;
context.boundParams = root->glob->boundParams; /* bound Params */
+ /* we do not need to mark the plan as depending on inlined functions */
+ context.glob = NULL;
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = true; /* unsafe transformations OK */
MemoryContextDelete(mycxt);
+ /*
+ * Since there is now no trace of the function in the plan tree, we
+ * must explicitly record the plan's dependency on the function.
+ */
+ if (context->glob)
+ record_plan_function_dependency(context->glob, funcid);
+
/*
* Recursively try to simplify the modified expression. Here we must add
* the current function to the context list of active functions.
error_context_stack = sqlerrcontext.previous;
ReleaseSysCache(func_tuple);
+ /*
+ * Since there is now no trace of the function in the plan tree, we
+ * must explicitly record the plan's dependency on the function.
+ */
+ record_plan_function_dependency(root->glob, fexpr->funcid);
+
return querytree;
/* Here if func is not inlinable: release temp memory and return NULL */
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_oper.c,v 1.105 2008/08/28 23:09:47 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_oper.c,v 1.106 2008/09/09 18:58:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
Oid ltypeId, Oid rtypeId);
static Oid find_oper_cache_entry(OprCacheKey *key);
static void make_oper_cache_entry(OprCacheKey *key, Oid opr_oid);
-static void InvalidateOprCacheCallBack(Datum arg, Oid relid);
+static void InvalidateOprCacheCallBack(Datum arg, int cacheid, ItemPointer tuplePtr);
/*
* Callback for pg_operator and pg_cast inval events
*/
static void
-InvalidateOprCacheCallBack(Datum arg, Oid relid)
+InvalidateOprCacheCallBack(Datum arg, int cacheid, ItemPointer tuplePtr)
{
HASH_SEQ_STATUS status;
OprCacheEntry *hentry;
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.141 2008/09/08 00:47:40 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.142 2008/09/09 18:58:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static AclMode convert_role_priv_string(text *priv_type_text);
static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
-static void RoleMembershipCacheCallback(Datum arg, Oid relid);
+static void RoleMembershipCacheCallback(Datum arg, int cacheid, ItemPointer tuplePtr);
/*
* Syscache inval callback function
*/
static void
-RoleMembershipCacheCallback(Datum arg, Oid relid)
+RoleMembershipCacheCallback(Datum arg, int cacheid, ItemPointer tuplePtr)
{
/* Force membership caches to be recomputed on next use */
cached_privs_role = InvalidOid;
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/cache/inval.c,v 1.86 2008/06/19 21:32:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/inval.c,v 1.87 2008/09/09 18:58:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* assumes there won't be very many of these at once; could improve if needed.
*/
-#define MAX_CACHE_CALLBACKS 20
+#define MAX_SYSCACHE_CALLBACKS 20
+#define MAX_RELCACHE_CALLBACKS 5
-static struct CACHECALLBACK
+static struct SYSCACHECALLBACK
{
- int16 id; /* cache number or message type id */
- CacheCallbackFunction function;
+ int16 id; /* cache number */
+ SyscacheCallbackFunction function;
Datum arg;
-} cache_callback_list[MAX_CACHE_CALLBACKS];
+} syscache_callback_list[MAX_SYSCACHE_CALLBACKS];
-static int cache_callback_count = 0;
+static int syscache_callback_count = 0;
+
+static struct RELCACHECALLBACK
+{
+ RelcacheCallbackFunction function;
+ Datum arg;
+} relcache_callback_list[MAX_RELCACHE_CALLBACKS];
+
+static int relcache_callback_count = 0;
/* info values for 2PC callback */
#define TWOPHASE_INFO_MSG 0 /* SharedInvalidationMessage */
msg->cc.hashValue,
&msg->cc.tuplePtr);
- for (i = 0; i < cache_callback_count; i++)
+ for (i = 0; i < syscache_callback_count; i++)
{
- struct CACHECALLBACK *ccitem = cache_callback_list + i;
+ struct SYSCACHECALLBACK *ccitem = syscache_callback_list + i;
if (ccitem->id == msg->cc.id)
- (*ccitem->function) (ccitem->arg, InvalidOid);
+ (*ccitem->function) (ccitem->arg,
+ msg->cc.id, &msg->cc.tuplePtr);
}
}
}
{
RelationCacheInvalidateEntry(msg->rc.relId);
- for (i = 0; i < cache_callback_count; i++)
+ for (i = 0; i < relcache_callback_count; i++)
{
- struct CACHECALLBACK *ccitem = cache_callback_list + i;
+ struct RELCACHECALLBACK *ccitem = relcache_callback_list + i;
- if (ccitem->id == SHAREDINVALRELCACHE_ID)
- (*ccitem->function) (ccitem->arg, msg->rc.relId);
+ (*ccitem->function) (ccitem->arg, msg->rc.relId);
}
}
}
ResetCatalogCaches();
RelationCacheInvalidate(); /* gets smgr cache too */
- for (i = 0; i < cache_callback_count; i++)
+ for (i = 0; i < syscache_callback_count; i++)
{
- struct CACHECALLBACK *ccitem = cache_callback_list + i;
+ struct SYSCACHECALLBACK *ccitem = syscache_callback_list + i;
+
+ (*ccitem->function) (ccitem->arg, ccitem->id, NULL);
+ }
+
+ for (i = 0; i < relcache_callback_count; i++)
+ {
+ struct RELCACHECALLBACK *ccitem = relcache_callback_list + i;
(*ccitem->function) (ccitem->arg, InvalidOid);
}
/*
* CacheRegisterSyscacheCallback
* Register the specified function to be called for all future
- * invalidation events in the specified cache.
+ * invalidation events in the specified cache. The cache ID and the
+ * TID of the tuple being invalidated will be passed to the function.
*
- * NOTE: currently, the OID argument to the callback routine is not
- * provided for syscache callbacks; the routine doesn't really get any
- * useful info as to exactly what changed. It should treat every call
- * as a "cache flush" request.
+ * NOTE: NULL will be passed for the TID if a cache reset request is received.
+ * In this case the called routines should flush all cached state.
*/
void
CacheRegisterSyscacheCallback(int cacheid,
- CacheCallbackFunction func,
+ SyscacheCallbackFunction func,
Datum arg)
{
- if (cache_callback_count >= MAX_CACHE_CALLBACKS)
- elog(FATAL, "out of cache_callback_list slots");
+ if (syscache_callback_count >= MAX_SYSCACHE_CALLBACKS)
+ elog(FATAL, "out of syscache_callback_list slots");
- cache_callback_list[cache_callback_count].id = cacheid;
- cache_callback_list[cache_callback_count].function = func;
- cache_callback_list[cache_callback_count].arg = arg;
+ syscache_callback_list[syscache_callback_count].id = cacheid;
+ syscache_callback_list[syscache_callback_count].function = func;
+ syscache_callback_list[syscache_callback_count].arg = arg;
- ++cache_callback_count;
+ ++syscache_callback_count;
}
/*
* In this case the called routines should flush all cached state.
*/
void
-CacheRegisterRelcacheCallback(CacheCallbackFunction func,
+CacheRegisterRelcacheCallback(RelcacheCallbackFunction func,
Datum arg)
{
- if (cache_callback_count >= MAX_CACHE_CALLBACKS)
- elog(FATAL, "out of cache_callback_list slots");
+ if (relcache_callback_count >= MAX_RELCACHE_CALLBACKS)
+ elog(FATAL, "out of relcache_callback_list slots");
- cache_callback_list[cache_callback_count].id = SHAREDINVALRELCACHE_ID;
- cache_callback_list[cache_callback_count].function = func;
- cache_callback_list[cache_callback_count].arg = arg;
+ relcache_callback_list[relcache_callback_count].function = func;
+ relcache_callback_list[relcache_callback_count].arg = arg;
- ++cache_callback_count;
+ ++relcache_callback_count;
}
*
* The plan cache manager itself is principally responsible for tracking
* whether cached plans should be invalidated because of schema changes in
- * the tables they depend on. When (and if) the next demand for a cached
+ * the objects they depend on. When (and if) the next demand for a cached
* plan occurs, the query will be replanned. Note that this could result
* in an error, for example if a column referenced by the query is no
* longer present. The creator of a cached plan can specify whether it
* 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 $
*
*-------------------------------------------------------------------------
*/
#include "catalog/namespace.h"
#include "executor/executor.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planmain.h"
#include "storage/lmgr.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
#include "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;
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);
}
/*
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;
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)
{
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;
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:
*/
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 ScanQueryForRelids
+ * Walker to find sublink subqueries for ScanQueryForLocks
*/
static bool
-ScanQueryWalker(Node *node, ScanQueryWalkerContext *context)
+ScanQueryWalker(Node *node, bool *acquire)
{
if (node == NULL)
return false;
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 ScanQueryForRelids already
+ * Do NOT recurse into Query nodes, because ScanQueryForLocks already
* processed subselects of subselects for us.
*/
return expression_tree_walker(node, ScanQueryWalker,
- (void *) context);
+ (void *) acquire);
}
/*
}
/*
- * PlanCacheCallback
+ * 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
-PlanCacheCallback(Datum arg, Oid relid)
+PlanCacheRelCallback(Datum arg, Oid relid)
{
ListCell *lc1;
- ListCell *lc2;
foreach(lc1, cached_plans_list)
{
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);
}
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);
+ ResetPlanCache();
}
/*
- * 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;
+ }
}
* Copyright (c) 2006-2008, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/cache/ts_cache.c,v 1.7 2008/04/12 23:14:21 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/ts_cache.c,v 1.8 2008/09/09 18:58:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/*
- * We use this catcache callback to detect when a visible change to a TS
+ * We use this syscache callback to detect when a visible change to a TS
* catalog entry has been made, by either our own backend or another one.
- * We don't get enough information to know *which* specific catalog row
- * changed, so we have to invalidate all related cache entries. Fortunately,
- * it seems unlikely that TS configuration changes will occur often enough
- * for this to be a performance problem.
+ *
+ * In principle we could just flush the specific cache entry that changed,
+ * but given that TS configuration changes are probably infrequent, it
+ * doesn't seem worth the trouble to determine that; we just flush all the
+ * entries of the related hash table.
*
* We can use the same function for all TS caches by passing the hash
* table address as the "arg".
*/
static void
-InvalidateTSCacheCallBack(Datum arg, Oid relid)
+InvalidateTSCacheCallBack(Datum arg, int cacheid, ItemPointer tuplePtr)
{
HTAB *hash = (HTAB *) DatumGetPointer(arg);
HASH_SEQ_STATUS status;
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/misc/superuser.c,v 1.37 2008/01/01 19:45:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/misc/superuser.c,v 1.38 2008/09/09 18:58:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static bool last_roleid_is_super = false;
static bool roleid_callback_registered = false;
-static void RoleidCallback(Datum arg, Oid relid);
+static void RoleidCallback(Datum arg, int cacheid, ItemPointer tuplePtr);
/*
* Syscache inval callback function
*/
static void
-RoleidCallback(Datum arg, Oid relid)
+RoleidCallback(Datum arg, int cacheid, ItemPointer tuplePtr)
{
/* Invalidate our local cache in case role's superuserness changed */
last_roleid = InvalidOid;
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.211 2008/08/30 01:39:14 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.212 2008/09/09 18:58:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
T_Hash,
T_SetOp,
T_Limit,
+ /* this one isn't a subclass of Plan: */
+ T_PlanInvalItem,
/*
* TAGS FOR PLAN STATE NODES (execnodes.h)
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.102 2008/08/07 19:35:02 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.103 2008/09/09 18:58:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "access/sdir.h"
#include "nodes/bitmapset.h"
#include "nodes/primnodes.h"
+#include "storage/itemptr.h"
/* ----------------------------------------------------------------
List *relationOids; /* OIDs of relations the plan depends on */
+ List *invalItems; /* other dependencies, as PlanInvalItems */
+
int nParamExec; /* number of PARAM_EXEC Params used */
} PlannedStmt;
Node *limitCount; /* COUNT parameter, or NULL if none */
} Limit;
+
+/*
+ * Plan invalidation info
+ *
+ * We track the objects on which a PlannedStmt depends in two ways:
+ * relations are recorded as a simple list of OIDs, and everything else
+ * is represented as a list of PlanInvalItems. A PlanInvalItem is designed
+ * to be used with the syscache invalidation mechanism, so it identifies a
+ * system catalog entry by cache ID and tuple TID.
+ */
+typedef struct PlanInvalItem
+{
+ NodeTag type;
+ int cacheId; /* a syscache ID, see utils/syscache.h */
+ ItemPointerData tupleId; /* TID of the object's catalog tuple */
+} PlanInvalItem;
+
#endif /* PLANNODES_H */
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.158 2008/08/14 18:48:00 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.159 2008/09/09 18:58:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
List *relationOids; /* OIDs of relations the plan depends on */
+ List *invalItems; /* other dependencies, as PlanInvalItems */
+
bool transientPlan; /* redo plan when TransactionXmin changes? */
} PlannerGlobal;
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.111 2008/08/14 18:48:00 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.112 2008/09/09 18:58:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
extern void fix_opfuncids(Node *node);
extern void set_opfuncid(OpExpr *opexpr);
extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
+extern void record_plan_function_dependency(PlannerGlobal *glob, Oid funcid);
+extern void extract_query_dependencies(List *queries,
+ List **relationOids,
+ List **invalItems);
#endif /* PLANMAIN_H */
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/utils/inval.h,v 1.43 2008/06/19 00:46:06 alvherre Exp $
+ * $PostgreSQL: pgsql/src/include/utils/inval.h,v 1.44 2008/09/09 18:58:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "utils/relcache.h"
-typedef void (*CacheCallbackFunction) (Datum arg, Oid relid);
+typedef void (*SyscacheCallbackFunction) (Datum arg, int cacheid, ItemPointer tuplePtr);
+typedef void (*RelcacheCallbackFunction) (Datum arg, Oid relid);
extern void AcceptInvalidationMessages(void);
extern void CacheInvalidateRelcacheByRelid(Oid relid);
extern void CacheRegisterSyscacheCallback(int cacheid,
- CacheCallbackFunction func,
+ SyscacheCallbackFunction func,
Datum arg);
-extern void CacheRegisterRelcacheCallback(CacheCallbackFunction func,
+extern void CacheRegisterRelcacheCallback(RelcacheCallbackFunction func,
Datum arg);
extern void inval_twophase_postcommit(TransactionId xid, uint16 info,
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/utils/plancache.h,v 1.12 2008/07/18 20:26:06 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/plancache.h,v 1.13 2008/09/09 18:58:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
int refcount; /* count of live references to this struct */
int generation; /* counter, starting at 1, for replans */
MemoryContext context; /* context containing this CachedPlan */
+ /* These fields are used only in the not-fully-planned case: */
+ List *relationOids; /* OIDs of relations the stmts depend on */
+ List *invalItems; /* other dependencies, as PlanInvalItems */
} CachedPlan;