From ee33b95d9c2ecec170bc517783d7268a4bd0c793 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 9 Sep 2008 18:58:09 +0000
Subject: [PATCH] Improve the plan cache invalidation mechanism to make it
 invalidate plans when user-defined functions used in a plan are modified. 
 Also invalidate plans when schemas, operators, or operator classes are
 modified; but for these cases we just invalidate everything rather than
 tracking exact dependencies, since these types of objects seldom change in a
 production database.

Tom Lane; loosely based on a patch by Martin Pihlak.
---
 src/backend/catalog/namespace.c      |   6 +-
 src/backend/nodes/copyfuncs.c        |  21 +-
 src/backend/nodes/outfuncs.c         |  28 ++-
 src/backend/optimizer/plan/planner.c |   4 +-
 src/backend/optimizer/plan/setrefs.c | 285 ++++++++++++++++++---------
 src/backend/optimizer/util/clauses.c |  24 ++-
 src/backend/parser/parse_oper.c      |   6 +-
 src/backend/utils/adt/acl.c          |   6 +-
 src/backend/utils/cache/inval.c      |  84 ++++----
 src/backend/utils/cache/plancache.c  | 260 ++++++++++++++----------
 src/backend/utils/cache/ts_cache.c   |  15 +-
 src/backend/utils/misc/superuser.c   |   6 +-
 src/include/nodes/nodes.h            |   4 +-
 src/include/nodes/plannodes.h        |  22 ++-
 src/include/nodes/relation.h         |   4 +-
 src/include/optimizer/planmain.h     |   6 +-
 src/include/utils/inval.h            |   9 +-
 src/include/utils/plancache.h        |   5 +-
 18 files changed, 521 insertions(+), 274 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index de827f74b2f..f1763e5199c 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -13,7 +13,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -187,7 +187,7 @@ static void recomputeNamespacePath(void);
 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);
@@ -3094,7 +3094,7 @@ InitializeSearchPath(void)
  *		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;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6e2028142b9..9cfa1700f43 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -88,6 +88,7 @@ _copyPlannedStmt(PlannedStmt *from)
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_NODE_FIELD(relationOids);
+	COPY_NODE_FIELD(invalItems);
 	COPY_SCALAR_FIELD(nParamExec);
 
 	return newnode;
@@ -689,6 +690,21 @@ _copyLimit(Limit *from)
 	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
  * ****************************************************************
@@ -3157,6 +3173,9 @@ copyObject(void *from)
 		case T_Limit:
 			retval = _copyLimit(from);
 			break;
+		case T_PlanInvalItem:
+			retval = _copyPlanInvalItem(from);
+			break;
 
 			/*
 			 * PRIMITIVE NODES
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 15ced48445a..0b74b3a063d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * 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*
@@ -255,6 +255,7 @@ _outPlannedStmt(StringInfo str, PlannedStmt *node)
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_NODE_FIELD(relationOids);
+	WRITE_NODE_FIELD(invalItems);
 	WRITE_INT_FIELD(nParamExec);
 }
 
@@ -593,6 +594,14 @@ _outUnique(StringInfo str, Unique *node)
 		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)
 {
@@ -631,11 +640,14 @@ _outLimit(StringInfo str, Limit *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));
 }
 
 /*****************************************************************************
@@ -1354,6 +1366,7 @@ _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(finalrtable);
 	WRITE_NODE_FIELD(relationOids);
+	WRITE_NODE_FIELD(invalItems);
 }
 
 static void
@@ -2206,14 +2219,17 @@ _outNode(StringInfo str, void *obj)
 			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);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 40a659391c6..ec2b0f794a0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -8,7 +8,7 @@
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -140,6 +140,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	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 */
@@ -213,6 +214,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->returningLists = root->returningLists;
 	result->rowMarks = parse->rowMarks;
 	result->relationOids = glob->relationOids;
+	result->invalItems = glob->invalItems;
 	result->nParamExec = list_length(glob->paramlist);
 
 	return result;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b9d5643da8a..18362628727 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -9,12 +9,13 @@
  *
  *
  * 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"
@@ -23,6 +24,7 @@
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 
 
 typedef struct
@@ -112,6 +114,8 @@ static Node *fix_upper_expr(PlannerGlobal *glob,
 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);
 
 
 /*****************************************************************************
@@ -138,10 +142,12 @@ static bool fix_opfuncids_walker(Node *node, void *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
@@ -164,7 +170,8 @@ static bool fix_opfuncids_walker(Node *node, void *context);
  * 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
@@ -622,6 +629,74 @@ copyVar(Var *var)
 	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
@@ -687,30 +762,7 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
 		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);
 }
@@ -720,25 +772,7 @@ fix_scan_expr_walker(Node *node, fix_scan_expr_context *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);
 }
@@ -1384,30 +1418,7 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *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);
@@ -1482,30 +1493,7 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *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);
@@ -1624,3 +1612,108 @@ set_sa_opfuncid(ScalarArrayOpExpr *opexpr)
 	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);
+}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 254b86e0d96..097105a89a2 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
  *
  *
  * 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
@@ -50,6 +50,7 @@
 typedef struct
 {
 	ParamListInfo boundParams;
+	PlannerGlobal *glob;
 	List	   *active_fns;
 	Node	   *case_val;
 	bool		estimate;
@@ -1890,9 +1891,15 @@ eval_const_expressions(PlannerInfo *root, Node *node)
 	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 */
@@ -1921,6 +1928,8 @@ estimate_expression_value(PlannerInfo *root, Node *node)
 	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 */
@@ -3468,6 +3477,13 @@ inline_function(Oid funcid, Oid result_type, List *args,
 
 	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.
@@ -3842,6 +3858,12 @@ inline_set_returning_function(PlannerInfo *root, Node *node)
 	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 */
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index 0ff5b59d00f..51114f7b466 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -8,7 +8,7 @@
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -79,7 +79,7 @@ static bool make_oper_cache_key(OprCacheKey *key, List *opname,
 								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);
 
 
 /*
@@ -1130,7 +1130,7 @@ make_oper_cache_entry(OprCacheKey *key, Oid opr_oid)
  * 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;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index d0d07751188..16d5f64fb09 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -8,7 +8,7 @@
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -92,7 +92,7 @@ static AclMode convert_tablespace_priv_string(text *priv_type_text);
 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);
 
 
 /*
@@ -2822,7 +2822,7 @@ initialize_acl(void)
  *		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;
diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c
index 050d7cc88de..5c9451022d2 100644
--- a/src/backend/utils/cache/inval.c
+++ b/src/backend/utils/cache/inval.c
@@ -80,7 +80,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -160,16 +160,25 @@ static TransInvalidationInfo *transInvalInfo = NULL;
  * 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 */
@@ -484,12 +493,13 @@ LocalExecuteInvalidationMessage(SharedInvalidationMessage *msg)
 									 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);
 			}
 		}
 	}
@@ -499,12 +509,11 @@ LocalExecuteInvalidationMessage(SharedInvalidationMessage *msg)
 		{
 			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);
 			}
 		}
 	}
@@ -539,9 +548,16 @@ InvalidateSystemCaches(void)
 	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);
 	}
@@ -1177,26 +1193,25 @@ CacheInvalidateRelcacheByRelid(Oid relid)
 /*
  * 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;
 }
 
 /*
@@ -1209,15 +1224,14 @@ CacheRegisterSyscacheCallback(int cacheid,
  * 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;
 }
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index c0dc3649ac0..bf1adf16ecc 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -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 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
@@ -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 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;
@@ -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 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);
 }
 
 /*
@@ -863,17 +834,16 @@ PlanCacheComputeResultDesc(List *stmt_list)
 }
 
 /*
- * 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)
 	{
@@ -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);
+	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;
+	}
 }
diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c
index 81ff577125e..33b415ac6f0 100644
--- a/src/backend/utils/cache/ts_cache.c
+++ b/src/backend/utils/cache/ts_cache.c
@@ -20,7 +20,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -79,18 +79,19 @@ static Oid	TSCurrentConfigCache = InvalidOid;
 
 
 /*
- * 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;
diff --git a/src/backend/utils/misc/superuser.c b/src/backend/utils/misc/superuser.c
index fd6407626a0..fdaa99e1f81 100644
--- a/src/backend/utils/misc/superuser.c
+++ b/src/backend/utils/misc/superuser.c
@@ -14,7 +14,7 @@
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -36,7 +36,7 @@ static Oid	last_roleid = InvalidOid;	/* InvalidOid == cache not valid */
 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);
 
 
 /*
@@ -102,7 +102,7 @@ superuser_arg(Oid roleid)
  *		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;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7e3d67a241f..c02b4c17904 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -67,6 +67,8 @@ typedef enum NodeTag
 	T_Hash,
 	T_SetOp,
 	T_Limit,
+	/* this one isn't a subclass of Plan: */
+	T_PlanInvalItem,
 
 	/*
 	 * TAGS FOR PLAN STATE NODES (execnodes.h)
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index f0e0d08e03a..2f0ed9a5d79 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -7,7 +7,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,6 +17,7 @@
 #include "access/sdir.h"
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
+#include "storage/itemptr.h"
 
 
 /* ----------------------------------------------------------------
@@ -72,6 +73,8 @@ typedef struct PlannedStmt
 
 	List	   *relationOids;	/* OIDs of relations the plan depends on */
 
+	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
+
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
 } PlannedStmt;
 
@@ -559,4 +562,21 @@ typedef struct Limit
 	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 */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 8fb6861e50c..c66bc05a749 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -7,7 +7,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -74,6 +74,8 @@ typedef struct PlannerGlobal
 
 	List	   *relationOids;	/* OIDs of relations the plan depends on */
 
+	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
+
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
 } PlannerGlobal;
 
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 1f15eda941a..a6b15187cea 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -7,7 +7,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -106,5 +106,9 @@ extern List *set_returning_clause_references(PlannerGlobal *glob,
 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 */
diff --git a/src/include/utils/inval.h b/src/include/utils/inval.h
index 821c751d280..0060d594082 100644
--- a/src/include/utils/inval.h
+++ b/src/include/utils/inval.h
@@ -7,7 +7,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,7 +18,8 @@
 #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);
@@ -50,10 +51,10 @@ extern void CacheInvalidateRelcacheByTuple(HeapTuple classTuple);
 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,
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 878ba7907a3..e19da85e93a 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -8,7 +8,7 @@
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -79,6 +79,9 @@ typedef struct CachedPlan
 	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;
 
 
-- 
GitLab