diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 1ffc823..544f423 100644
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 62,67 ****
--- 62,68 ----
  #include "parser/parse_type.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
+ #include "utils/inval.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/rel.h"
*************** AlterDomainDefault(List *names, Node *de
*** 2297,2303 ****
  	ObjectAddressSet(address, TypeRelationId, domainoid);
  
  	/* Clean up */
! 	heap_close(rel, NoLock);
  	heap_freetuple(newtuple);
  
  	return address;
--- 2298,2304 ----
  	ObjectAddressSet(address, TypeRelationId, domainoid);
  
  	/* Clean up */
! 	heap_close(rel, RowExclusiveLock);
  	heap_freetuple(newtuple);
  
  	return address;
*************** AlterDomainDropConstraint(List *names, c
*** 2494,2501 ****
  	systable_endscan(conscan);
  	heap_close(conrel, RowExclusiveLock);
  
- 	heap_close(rel, NoLock);
- 
  	if (!found)
  	{
  		if (!missing_ok)
--- 2495,2500 ----
*************** AlterDomainDropConstraint(List *names, c
*** 2509,2516 ****
--- 2508,2525 ----
  							constrName, TypeNameToString(typename))));
  	}
  
+ 	/*
+ 	 * We must send out an sinval message for the domain, to ensure that any
+ 	 * dependent plans get rebuilt.  Since this command doesn't change the
+ 	 * domain's pg_type row, that won't happen automatically; do it manually.
+ 	 */
+ 	CacheInvalidateHeapTuple(rel, tup, NULL);
+ 
  	ObjectAddressSet(address, TypeRelationId, domainoid);
  
+ 	/* Clean up */
+ 	heap_close(rel, RowExclusiveLock);
+ 
  	return address;
  }
  
*************** AlterDomainAddConstraint(List *names, No
*** 2615,2620 ****
--- 2624,2636 ----
  	if (!constr->skip_validation)
  		validateDomainConstraint(domainoid, ccbin);
  
+ 	/*
+ 	 * We must send out an sinval message for the domain, to ensure that any
+ 	 * dependent plans get rebuilt.  Since this command doesn't change the
+ 	 * domain's pg_type row, that won't happen automatically; do it manually.
+ 	 */
+ 	CacheInvalidateHeapTuple(typrel, tup, NULL);
+ 
  	ObjectAddressSet(address, TypeRelationId, domainoid);
  
  	/* Clean up */
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index c729a99..b645648 100644
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** adjust_paths_for_srfs(PlannerInfo *root,
*** 5923,5932 ****
   * side-effect that is useful when the expression will get evaluated more than
   * once.  Also, we must fix operator function IDs.
   *
   * Note: this must not make any damaging changes to the passed-in expression
   * tree.  (It would actually be okay to apply fix_opfuncids to it, but since
   * we first do an expression_tree_mutator-based walk, what is returned will
!  * be a new node tree.)
   */
  Expr *
  expression_planner(Expr *expr)
--- 5923,5938 ----
   * side-effect that is useful when the expression will get evaluated more than
   * once.  Also, we must fix operator function IDs.
   *
+  * This does not return any information about dependencies of the expression.
+  * Hence callers should use the results only for the duration of the current
+  * query.  Callers that would like to cache the results for longer should use
+  * expression_planner_with_deps, probably via the plancache.
+  *
   * Note: this must not make any damaging changes to the passed-in expression
   * tree.  (It would actually be okay to apply fix_opfuncids to it, but since
   * we first do an expression_tree_mutator-based walk, what is returned will
!  * be a new node tree.)  The result is constructed in the current memory
!  * context; beware that this can leak a lot of additional stuff there, too.
   */
  Expr *
  expression_planner(Expr *expr)
*************** expression_planner(Expr *expr)
*** 5945,5950 ****
--- 5951,6007 ----
  	return (Expr *) result;
  }
  
+ /*
+  * expression_planner_with_deps
+  *		Perform planner's transformations on a standalone expression,
+  *		returning expression dependency information along with the result.
+  *
+  * This is identical to expression_planner() except that it also returns
+  * information about possible dependencies of the expression, ie identities of
+  * objects whose definitions affect the result.  As in a PlannedStmt, these
+  * are expressed as a list of relation Oids and a list of PlanInvalItems.
+  */
+ Expr *
+ expression_planner_with_deps(Expr *expr,
+ 							 List **relationOids,
+ 							 List **invalItems)
+ {
+ 	Node	   *result;
+ 	PlannerGlobal glob;
+ 	PlannerInfo root;
+ 
+ 	/* Make up dummy planner state so we can use setrefs machinery */
+ 	MemSet(&glob, 0, sizeof(glob));
+ 	glob.type = T_PlannerGlobal;
+ 	glob.relationOids = NIL;
+ 	glob.invalItems = NIL;
+ 
+ 	MemSet(&root, 0, sizeof(root));
+ 	root.type = T_PlannerInfo;
+ 	root.glob = &glob;
+ 
+ 	/*
+ 	 * Convert named-argument function calls, insert default arguments and
+ 	 * simplify constant subexprs.  Collect identities of inlined functions
+ 	 * and elided domains, too.
+ 	 */
+ 	result = eval_const_expressions(&root, (Node *) expr);
+ 
+ 	/* Fill in opfuncid values if missing */
+ 	fix_opfuncids(result);
+ 
+ 	/*
+ 	 * Now walk the finished expression to find anything else we ought to
+ 	 * record as an expression dependency.
+ 	 */
+ 	(void) extract_query_dependencies_walker(result, &root);
+ 
+ 	*relationOids = glob.relationOids;
+ 	*invalItems = glob.invalItems;
+ 
+ 	return (Expr *) result;
+ }
+ 
  
  /*
   * plan_cluster_use_sort
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 6d6ef1c..a718e15 100644
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** static List *set_returning_clause_refere
*** 138,145 ****
  								Plan *topplan,
  								Index resultRelation,
  								int rtoffset);
! static bool extract_query_dependencies_walker(Node *node,
! 								  PlannerInfo *context);
  
  /*****************************************************************************
   *
--- 138,144 ----
  								Plan *topplan,
  								Index resultRelation,
  								int rtoffset);
! 
  
  /*****************************************************************************
   *
*************** static bool extract_query_dependencies_w
*** 175,182 ****
   * This will be used by plancache.c to drive invalidation of cached plans.
   * 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.
   *
   * 8. We assign every plan node in the tree a unique ID.
   *
--- 174,181 ----
   * This will be used by plancache.c to drive invalidation of cached plans.
   * Relation dependencies are represented by OIDs, and everything else by
   * PlanInvalItems (this distinction is motivated by the shared-inval APIs).
!  * Currently, relations, user-defined functions, and domains are the only
!  * types of objects that are explicitly tracked this way.
   *
   * 8. We assign every plan node in the tree a unique ID.
   *
*************** record_plan_function_dependency(PlannerI
*** 2576,2581 ****
--- 2575,2616 ----
  }
  
  /*
+  * record_plan_type_dependency
+  *		Mark the current plan as depending on a particular type.
+  *
+  * This is exported so that eval_const_expressions can record a
+  * dependency on a domain that it's removed a CoerceToDomain node for.
+  *
+  * We don't currently need to record dependencies on domains that the
+  * plan contains CoerceToDomain nodes for, though that might change in
+  * future.  Hence, this isn't actually called in this module, though
+  * someday fix_expr_common might call it.
+  */
+ void
+ record_plan_type_dependency(PlannerInfo *root, Oid typeid)
+ {
+ 	/*
+ 	 * As in record_plan_function_dependency, ignore the possibility that
+ 	 * someone would change a built-in domain.
+ 	 */
+ 	if (typeid >= (Oid) FirstBootstrapObjectId)
+ 	{
+ 		PlanInvalItem *inval_item = makeNode(PlanInvalItem);
+ 
+ 		/*
+ 		 * It would work to use any syscache on pg_type, but the easiest is
+ 		 * TYPEOID since we already have the type's OID at hand.  Note that
+ 		 * plancache.c knows we use TYPEOID.
+ 		 */
+ 		inval_item->cacheId = TYPEOID;
+ 		inval_item->hashValue = GetSysCacheHashValue1(TYPEOID,
+ 													  ObjectIdGetDatum(typeid));
+ 
+ 		root->glob->invalItems = lappend(root->glob->invalItems, inval_item);
+ 	}
+ }
+ 
+ /*
   * extract_query_dependencies
   *		Given a rewritten, but not yet planned, query or queries
   *		(i.e. a Query node or list of Query nodes), extract dependencies
*************** record_plan_function_dependency(PlannerI
*** 2584,2589 ****
--- 2619,2631 ----
   *
   * This is needed by plancache.c to handle invalidation of cached unplanned
   * queries.
+  *
+  * Note: this does not go through eval_const_expressions, and hence doesn't
+  * reflect its additions of inlined functions and elided CoerceToDomain nodes
+  * to the invalItems list.  This is obviously OK for functions, since we'll
+  * see them in the original query tree anyway.  For domains, it's OK because
+  * we don't care about domains unless they get elided.  That is, a plan might
+  * have domain dependencies that the query tree doesn't.
   */
  void
  extract_query_dependencies(Node *query,
*************** extract_query_dependencies(Node *query,
*** 2613,2626 ****
  	*hasRowSecurity = glob.dependsOnRole;
  }
  
! static bool
  extract_query_dependencies_walker(Node *node, PlannerInfo *context)
  {
  	if (node == NULL)
  		return false;
  	Assert(!IsA(node, PlaceHolderVar));
- 	/* Extract function dependencies and check for regclass Consts */
- 	fix_expr_common(context, node);
  	if (IsA(node, Query))
  	{
  		Query	   *query = (Query *) node;
--- 2655,2674 ----
  	*hasRowSecurity = glob.dependsOnRole;
  }
  
! /*
!  * Tree walker for extract_query_dependencies.
!  *
!  * This is exported so that expression_planner_with_deps can call it on
!  * simple expressions (post-planning, not before planning, in that case).
!  * In that usage, glob.dependsOnRole isn't meaningful, but the relationOids
!  * and invalItems lists are added to as needed.
!  */
! bool
  extract_query_dependencies_walker(Node *node, PlannerInfo *context)
  {
  	if (node == NULL)
  		return false;
  	Assert(!IsA(node, PlaceHolderVar));
  	if (IsA(node, Query))
  	{
  		Query	   *query = (Query *) node;
*************** extract_query_dependencies_walker(Node *
*** 2660,2665 ****
--- 2708,2715 ----
  		return query_tree_walker(query, extract_query_dependencies_walker,
  								 (void *) context, 0);
  	}
+ 	/* Extract function dependencies and check for regclass Consts */
+ 	fix_expr_common(context, node);
  	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 8df3693..f444616 100644
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
*************** eval_const_expressions_mutator(Node *nod
*** 3699,3704 ****
--- 3699,3768 ----
  				newbtest->location = btest->location;
  				return (Node *) newbtest;
  			}
+ 		case T_CoerceToDomain:
+ 			{
+ 				/*
+ 				 * If the domain currently has no constraints, we replace the
+ 				 * CoerceToDomain node with a simple RelabelType, which is
+ 				 * both far faster to execute and more amenable to later
+ 				 * optimization.  We must then mark the plan as needing to be
+ 				 * rebuilt if the domain's constraints change.
+ 				 *
+ 				 * Also, in estimation mode, always replace CoerceToDomain
+ 				 * nodes, effectively assuming that the coercion will succeed.
+ 				 */
+ 				CoerceToDomain *cdomain = (CoerceToDomain *) node;
+ 				CoerceToDomain *newcdomain;
+ 				Node	   *arg;
+ 
+ 				arg = eval_const_expressions_mutator((Node *) cdomain->arg,
+ 													 context);
+ 				if (context->estimate ||
+ 					!DomainHasConstraints(cdomain->resulttype))
+ 				{
+ 					/* Record dependency, if this isn't estimation mode */
+ 					if (context->root && !context->estimate)
+ 						record_plan_type_dependency(context->root,
+ 													cdomain->resulttype);
+ 
+ 					/* Generate RelabelType to substitute for CoerceToDomain */
+ 					/* This should match the RelabelType logic above */
+ 
+ 					while (arg && IsA(arg, RelabelType))
+ 						arg = (Node *) ((RelabelType *) arg)->arg;
+ 
+ 					if (arg && IsA(arg, Const))
+ 					{
+ 						Const	   *con = (Const *) arg;
+ 
+ 						con->consttype = cdomain->resulttype;
+ 						con->consttypmod = cdomain->resulttypmod;
+ 						con->constcollid = cdomain->resultcollid;
+ 						return (Node *) con;
+ 					}
+ 					else
+ 					{
+ 						RelabelType *newrelabel = makeNode(RelabelType);
+ 
+ 						newrelabel->arg = (Expr *) arg;
+ 						newrelabel->resulttype = cdomain->resulttype;
+ 						newrelabel->resulttypmod = cdomain->resulttypmod;
+ 						newrelabel->resultcollid = cdomain->resultcollid;
+ 						newrelabel->relabelformat = cdomain->coercionformat;
+ 						newrelabel->location = cdomain->location;
+ 						return (Node *) newrelabel;
+ 					}
+ 				}
+ 
+ 				newcdomain = makeNode(CoerceToDomain);
+ 				newcdomain->arg = (Expr *) arg;
+ 				newcdomain->resulttype = cdomain->resulttype;
+ 				newcdomain->resulttypmod = cdomain->resulttypmod;
+ 				newcdomain->resultcollid = cdomain->resultcollid;
+ 				newcdomain->coercionformat = cdomain->coercionformat;
+ 				newcdomain->location = cdomain->location;
+ 				return (Node *) newcdomain;
+ 			}
  		case T_PlaceHolderVar:
  
  			/*
*************** eval_const_expressions_mutator(Node *nod
*** 3770,3776 ****
  	 * For any node type not handled above, copy the node unchanged but
  	 * const-simplify its subexpressions.  This is the correct thing for node
  	 * types whose behavior might change between planning and execution, such
! 	 * as CoerceToDomain.  It's also a safe default for new node types not
  	 * known to this routine.
  	 */
  	return ece_generic_processing(node);
--- 3834,3840 ----
  	 * For any node type not handled above, copy the node unchanged but
  	 * const-simplify its subexpressions.  This is the correct thing for node
  	 * types whose behavior might change between planning and execution, such
! 	 * as CurrentOfExpr.  It's also a safe default for new node types not
  	 * known to this routine.
  	 */
  	return ece_generic_processing(node);
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 9ec81c5..a3898ec 100644
*** a/src/backend/utils/cache/plancache.c
--- b/src/backend/utils/cache/plancache.c
***************
*** 27,41 ****
   * query to change output tupdesc on replan --- if so, it's up to the
   * caller to notice changes and cope with them.
   *
!  * Currently, we track exactly the dependencies of plans on relations 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-2018, PostgreSQL Global Development Group
--- 27,47 ----
   * query to change output tupdesc on replan --- if so, it's up to the
   * caller to notice changes and cope with them.
   *
!  * Currently, we track exactly the dependencies of plans on relations,
!  * user-defined functions, and domains.  On relcache invalidation events or
!  * pg_proc or pg_type syscache invalidation events, we invalidate just those
!  * plans that depend on the particular object being modified.  (Note: this
!  * scheme assumes that any table modification that requires replanning will
!  * generate a relcache inval event.)  We also watch for inval events on
!  * certain other system catalogs, such as pg_namespace; but for them, our
!  * response is just to invalidate all plans.  We expect updates on those
!  * catalogs to be infrequent enough that more-detailed tracking is not worth
!  * the effort.
!  *
!  * In addition to full-fledged query plans, we provide a facility for
!  * detecting invalidations of simple scalar expressions.  This is fairly
!  * bare-bones; it's the caller's responsibility to build a new expression
!  * if the old one gets invalidated.
   *
   *
   * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
***************
*** 57,62 ****
--- 63,69 ----
  #include "nodes/nodeFuncs.h"
  #include "optimizer/cost.h"
  #include "optimizer/planmain.h"
+ #include "optimizer/planner.h"
  #include "optimizer/prep.h"
  #include "parser/analyze.h"
  #include "parser/parsetree.h"
***************
*** 82,91 ****
  /*
   * This is the head of the backend's list of "saved" CachedPlanSources (i.e.,
   * those that are in long-lived storage and are examined for sinval events).
!  * We thread the structs manually instead of using List cells so that we can
!  * guarantee to save a CachedPlanSource without error.
   */
! static CachedPlanSource *first_saved_plan = NULL;
  
  static void ReleaseGenericPlan(CachedPlanSource *plansource);
  static List *RevalidateCachedQuery(CachedPlanSource *plansource,
--- 89,103 ----
  /*
   * This is the head of the backend's list of "saved" CachedPlanSources (i.e.,
   * those that are in long-lived storage and are examined for sinval events).
!  * We use a dlist instead of separate List cells so that we can guarantee
!  * to save a CachedPlanSource without error.
   */
! static dlist_head saved_plan_list;
! 
! /*
!  * This is the head of the backend's list of CachedExpressions.
!  */
! static dlist_head cached_expression_list;
  
  static void ReleaseGenericPlan(CachedPlanSource *plansource);
  static List *RevalidateCachedQuery(CachedPlanSource *plansource,
*************** static void ScanQueryForLocks(Query *par
*** 103,109 ****
  static bool ScanQueryWalker(Node *node, bool *acquire);
  static TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
  static void PlanCacheRelCallback(Datum arg, Oid relid);
! static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue);
  static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
  
  /* GUC parameter */
--- 115,121 ----
  static bool ScanQueryWalker(Node *node, bool *acquire);
  static TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
  static void PlanCacheRelCallback(Datum arg, Oid relid);
! static void PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue);
  static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
  
  /* GUC parameter */
*************** void
*** 118,124 ****
  InitPlanCache(void)
  {
  	CacheRegisterRelcacheCallback(PlanCacheRelCallback, (Datum) 0);
! 	CacheRegisterSyscacheCallback(PROCOID, PlanCacheFuncCallback, (Datum) 0);
  	CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0);
  	CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0);
  	CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
--- 130,137 ----
  InitPlanCache(void)
  {
  	CacheRegisterRelcacheCallback(PlanCacheRelCallback, (Datum) 0);
! 	CacheRegisterSyscacheCallback(PROCOID, PlanCacheObjectCallback, (Datum) 0);
! 	CacheRegisterSyscacheCallback(TYPEOID, PlanCacheObjectCallback, (Datum) 0);
  	CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0);
  	CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0);
  	CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
*************** CreateCachedPlan(RawStmt *raw_parse_tree
*** 206,212 ****
  	plansource->is_saved = false;
  	plansource->is_valid = false;
  	plansource->generation = 0;
- 	plansource->next_saved = NULL;
  	plansource->generic_cost = -1;
  	plansource->total_custom_cost = 0;
  	plansource->num_custom_plans = 0;
--- 219,224 ----
*************** CreateOneShotCachedPlan(RawStmt *raw_par
*** 274,280 ****
  	plansource->is_saved = false;
  	plansource->is_valid = false;
  	plansource->generation = 0;
- 	plansource->next_saved = NULL;
  	plansource->generic_cost = -1;
  	plansource->total_custom_cost = 0;
  	plansource->num_custom_plans = 0;
--- 286,291 ----
*************** SaveCachedPlan(CachedPlanSource *plansou
*** 471,478 ****
  	/*
  	 * Add the entry to the global list of cached plans.
  	 */
! 	plansource->next_saved = first_saved_plan;
! 	first_saved_plan = plansource;
  
  	plansource->is_saved = true;
  }
--- 482,488 ----
  	/*
  	 * Add the entry to the global list of cached plans.
  	 */
! 	dlist_push_tail(&saved_plan_list, &plansource->node);
  
  	plansource->is_saved = true;
  }
*************** DropCachedPlan(CachedPlanSource *plansou
*** 493,513 ****
  	/* If it's been saved, remove it from the list */
  	if (plansource->is_saved)
  	{
! 		if (first_saved_plan == plansource)
! 			first_saved_plan = plansource->next_saved;
! 		else
! 		{
! 			CachedPlanSource *psrc;
! 
! 			for (psrc = first_saved_plan; psrc; psrc = psrc->next_saved)
! 			{
! 				if (psrc->next_saved == plansource)
! 				{
! 					psrc->next_saved = plansource->next_saved;
! 					break;
! 				}
! 			}
! 		}
  		plansource->is_saved = false;
  	}
  
--- 503,509 ----
  	/* If it's been saved, remove it from the list */
  	if (plansource->is_saved)
  	{
! 		dlist_delete(&plansource->node);
  		plansource->is_saved = false;
  	}
  
*************** CopyCachedPlan(CachedPlanSource *plansou
*** 1399,1405 ****
  	newsource->is_saved = false;
  	newsource->is_valid = plansource->is_valid;
  	newsource->generation = plansource->generation;
- 	newsource->next_saved = NULL;
  
  	/* We may as well copy any acquired cost knowledge */
  	newsource->generic_cost = plansource->generic_cost;
--- 1395,1400 ----
*************** CachedPlanGetTargetList(CachedPlanSource
*** 1459,1464 ****
--- 1454,1538 ----
  }
  
  /*
+  * GetCachedExpression: construct a CachedExpression for an expression.
+  *
+  * This performs the same transformations on the expression as
+  * expression_planner(), ie, convert an expression as emitted by parse
+  * analysis to be ready to pass to the executor.
+  *
+  * The result is stashed in a private, long-lived memory context.
+  * (Note that this might leak a good deal of memory in the caller's
+  * context before that.)  The passed-in expr tree is not modified.
+  */
+ CachedExpression *
+ GetCachedExpression(Node *expr)
+ {
+ 	CachedExpression *cexpr;
+ 	List	   *relationOids;
+ 	List	   *invalItems;
+ 	MemoryContext cexpr_context;
+ 	MemoryContext oldcxt;
+ 
+ 	/*
+ 	 * Pass the expression through the planner, and collect dependencies.
+ 	 * Everything built here is leaked in the caller's context; that's
+ 	 * intentional to minimize the size of the permanent data structure.
+ 	 */
+ 	expr = (Node *) expression_planner_with_deps((Expr *) expr,
+ 												 &relationOids,
+ 												 &invalItems);
+ 
+ 	/*
+ 	 * Make a private memory context, and copy what we need into that.  To
+ 	 * avoid leaking a long-lived context if we fail while copying data, we
+ 	 * initially make the context under the caller's context.
+ 	 */
+ 	cexpr_context = AllocSetContextCreate(CurrentMemoryContext,
+ 										  "CachedExpression",
+ 										  ALLOCSET_SMALL_SIZES);
+ 
+ 	oldcxt = MemoryContextSwitchTo(cexpr_context);
+ 
+ 	cexpr = (CachedExpression *) palloc(sizeof(CachedExpression));
+ 	cexpr->magic = CACHEDEXPR_MAGIC;
+ 	cexpr->expr = copyObject(expr);
+ 	cexpr->is_valid = true;
+ 	cexpr->relationOids = copyObject(relationOids);
+ 	cexpr->invalItems = copyObject(invalItems);
+ 	cexpr->context = cexpr_context;
+ 
+ 	MemoryContextSwitchTo(oldcxt);
+ 
+ 	/*
+ 	 * Reparent the expr's memory context under CacheMemoryContext so that it
+ 	 * will live indefinitely.
+ 	 */
+ 	MemoryContextSetParent(cexpr->context, CacheMemoryContext);
+ 
+ 	/*
+ 	 * Add the entry to the global list of cached expressions.
+ 	 */
+ 	dlist_push_tail(&cached_expression_list, &cexpr->node);
+ 
+ 	return cexpr;
+ }
+ 
+ /*
+  * FreeCachedExpression
+  *		Delete a CachedExpression.
+  */
+ void
+ FreeCachedExpression(CachedExpression *cexpr)
+ {
+ 	/* Sanity check */
+ 	Assert(cexpr->magic == CACHEDEXPR_MAGIC);
+ 	/* Unlink from global list */
+ 	dlist_delete(&cexpr->node);
+ 	/* Free all storage associated with CachedExpression */
+ 	MemoryContextDelete(cexpr->context);
+ }
+ 
+ /*
   * QueryListGetPrimaryStmt
   *		Get the "primary" stmt within a list, ie, the one marked canSetTag.
   *
*************** PlanCacheComputeResultDesc(List *stmt_li
*** 1692,1701 ****
  static void
  PlanCacheRelCallback(Datum arg, Oid relid)
  {
! 	CachedPlanSource *plansource;
  
! 	for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved)
  	{
  		Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
  
  		/* No work if it's already invalidated */
--- 1766,1778 ----
  static void
  PlanCacheRelCallback(Datum arg, Oid relid)
  {
! 	dlist_iter	iter;
  
! 	dlist_foreach(iter, &saved_plan_list)
  	{
+ 		CachedPlanSource *plansource = dlist_container(CachedPlanSource,
+ 													   node, iter.cur);
+ 
  		Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
  
  		/* No work if it's already invalidated */
*************** PlanCacheRelCallback(Datum arg, Oid reli
*** 1742,1766 ****
  			}
  		}
  	}
  }
  
  /*
!  * PlanCacheFuncCallback
!  *		Syscache inval callback function for PROCOID cache
   *
   * Invalidate all plans mentioning the object with the specified hash value,
   * or all plans mentioning any member of this cache if hashvalue == 0.
-  *
-  * 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, uint32 hashvalue)
  {
! 	CachedPlanSource *plansource;
  
! 	for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved)
  	{
  		ListCell   *lc;
  
  		Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
--- 1819,1861 ----
  			}
  		}
  	}
+ 
+ 	/* Likewise check cached expressions */
+ 	dlist_foreach(iter, &cached_expression_list)
+ 	{
+ 		CachedExpression *cexpr = dlist_container(CachedExpression,
+ 												  node, iter.cur);
+ 
+ 		Assert(cexpr->magic == CACHEDEXPR_MAGIC);
+ 
+ 		/* No work if it's already invalidated */
+ 		if (!cexpr->is_valid)
+ 			continue;
+ 
+ 		if ((relid == InvalidOid) ? cexpr->relationOids != NIL :
+ 			list_member_oid(cexpr->relationOids, relid))
+ 		{
+ 			cexpr->is_valid = false;
+ 		}
+ 	}
  }
  
  /*
!  * PlanCacheObjectCallback
!  *		Syscache inval callback function for PROCOID and TYPEOID caches
   *
   * Invalidate all plans mentioning the object with the specified hash value,
   * or all plans mentioning any member of this cache if hashvalue == 0.
   */
  static void
! PlanCacheObjectCallback(Datum arg, int cacheid, uint32 hashvalue)
  {
! 	dlist_iter	iter;
  
! 	dlist_foreach(iter, &saved_plan_list)
  	{
+ 		CachedPlanSource *plansource = dlist_container(CachedPlanSource,
+ 													   node, iter.cur);
  		ListCell   *lc;
  
  		Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
*************** PlanCacheFuncCallback(Datum arg, int cac
*** 1825,1830 ****
--- 1920,1953 ----
  			}
  		}
  	}
+ 
+ 	/* Likewise check cached expressions */
+ 	dlist_foreach(iter, &cached_expression_list)
+ 	{
+ 		CachedExpression *cexpr = dlist_container(CachedExpression,
+ 												  node, iter.cur);
+ 		ListCell   *lc;
+ 
+ 		Assert(cexpr->magic == CACHEDEXPR_MAGIC);
+ 
+ 		/* No work if it's already invalidated */
+ 		if (!cexpr->is_valid)
+ 			continue;
+ 
+ 		foreach(lc, cexpr->invalItems)
+ 		{
+ 			PlanInvalItem *item = (PlanInvalItem *) lfirst(lc);
+ 
+ 			if (item->cacheId != cacheid)
+ 				continue;
+ 			if (hashvalue == 0 ||
+ 				item->hashValue == hashvalue)
+ 			{
+ 				cexpr->is_valid = false;
+ 				break;
+ 			}
+ 		}
+ 	}
  }
  
  /*
*************** PlanCacheSysCallback(Datum arg, int cach
*** 1845,1854 ****
  void
  ResetPlanCache(void)
  {
! 	CachedPlanSource *plansource;
  
! 	for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved)
  	{
  		ListCell   *lc;
  
  		Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
--- 1968,1979 ----
  void
  ResetPlanCache(void)
  {
! 	dlist_iter	iter;
  
! 	dlist_foreach(iter, &saved_plan_list)
  	{
+ 		CachedPlanSource *plansource = dlist_container(CachedPlanSource,
+ 													   node, iter.cur);
  		ListCell   *lc;
  
  		Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
*************** ResetPlanCache(void)
*** 1888,1891 ****
--- 2013,2027 ----
  			}
  		}
  	}
+ 
+ 	/* Likewise invalidate cached expressions */
+ 	dlist_foreach(iter, &cached_expression_list)
+ 	{
+ 		CachedExpression *cexpr = dlist_container(CachedExpression,
+ 												  node, iter.cur);
+ 
+ 		Assert(cexpr->magic == CACHEDEXPR_MAGIC);
+ 
+ 		cexpr->is_valid = false;
+ 	}
  }
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 09f9d5f..1a96cc9 100644
*** a/src/backend/utils/cache/typcache.c
--- b/src/backend/utils/cache/typcache.c
*************** load_domaintype_info(TypeCacheEntry *typ
*** 992,998 ****
  
  			check_expr = (Expr *) stringToNode(constring);
  
! 			/* ExecInitExpr will assume we've planned the expression */
  			check_expr = expression_planner(check_expr);
  
  			r = makeNode(DomainConstraintState);
--- 992,1007 ----
  
  			check_expr = (Expr *) stringToNode(constring);
  
! 			/*
! 			 * Plan the expression, since ExecInitExpr will expect that.
! 			 *
! 			 * Note: caching the result of expression_planner() is not very
! 			 * good practice.  Ideally we'd use a CachedExpression here so
! 			 * that we would react promptly to, eg, changes in inlined
! 			 * functions.  However, because we don't support mutable domain
! 			 * CHECK constraints, it's not really clear that it's worth the
! 			 * extra overhead to do that.
! 			 */
  			check_expr = expression_planner(check_expr);
  
  			r = makeNode(DomainConstraintState);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index c8ab028..2b1932d 100644
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
*************** extern bool innerrel_is_unique(PlannerIn
*** 116,124 ****
--- 116,126 ----
   */
  extern Plan *set_plan_references(PlannerInfo *root, Plan *plan);
  extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
+ extern void record_plan_type_dependency(PlannerInfo *root, Oid typeid);
  extern void extract_query_dependencies(Node *query,
  						   List **relationOids,
  						   List **invalItems,
  						   bool *hasRowSecurity);
+ extern bool extract_query_dependencies_walker(Node *node, PlannerInfo *root);
  
  #endif							/* PLANMAIN_H */
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 3e733b3..902ab40 100644
*** a/src/include/optimizer/planner.h
--- b/src/include/optimizer/planner.h
*************** extern Path *get_cheapest_fractional_pat
*** 55,60 ****
--- 55,63 ----
  							 double tuple_fraction);
  
  extern Expr *expression_planner(Expr *expr);
+ extern Expr *expression_planner_with_deps(Expr *expr,
+ 							 List **relationOids,
+ 							 List **invalItems);
  
  extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);
  
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 5fc7903..7fd7ea6 100644
*** a/src/include/utils/plancache.h
--- b/src/include/utils/plancache.h
***************
*** 16,29 ****
--- 16,42 ----
  #define PLANCACHE_H
  
  #include "access/tupdesc.h"
+ #include "lib/ilist.h"
  #include "nodes/params.h"
  #include "utils/queryenvironment.h"
  
  /* Forward declaration, to avoid including parsenodes.h here */
  struct RawStmt;
  
+ /* possible values for plan_cache_mode */
+ typedef enum
+ {
+ 	PLAN_CACHE_MODE_AUTO,
+ 	PLAN_CACHE_MODE_FORCE_GENERIC_PLAN,
+ 	PLAN_CACHE_MODE_FORCE_CUSTOM_PLAN
+ }			PlanCacheMode;
+ 
+ /* GUC parameter */
+ extern int	plan_cache_mode;
+ 
  #define CACHEDPLANSOURCE_MAGIC		195726186
  #define CACHEDPLAN_MAGIC			953717834
+ #define CACHEDEXPR_MAGIC			838275847
  
  /*
   * CachedPlanSource (which might better have been called CachedQuery)
*************** typedef struct CachedPlanSource
*** 110,116 ****
  	bool		is_valid;		/* is the query_list currently valid? */
  	int			generation;		/* increments each time we create a plan */
  	/* If CachedPlanSource has been saved, it is a member of a global list */
! 	struct CachedPlanSource *next_saved;	/* list link, if so */
  	/* State kept to help decide whether to use custom or generic plans: */
  	double		generic_cost;	/* cost of generic plan, or -1 if not known */
  	double		total_custom_cost;	/* total cost of custom plans so far */
--- 123,129 ----
  	bool		is_valid;		/* is the query_list currently valid? */
  	int			generation;		/* increments each time we create a plan */
  	/* If CachedPlanSource has been saved, it is a member of a global list */
! 	dlist_node	node;			/* list link, if is_saved */
  	/* State kept to help decide whether to use custom or generic plans: */
  	double		generic_cost;	/* cost of generic plan, or -1 if not known */
  	double		total_custom_cost;	/* total cost of custom plans so far */
*************** typedef struct CachedPlan
*** 143,148 ****
--- 156,185 ----
  	MemoryContext context;		/* context containing this CachedPlan */
  } CachedPlan;
  
+ /*
+  * CachedExpression is a low-overhead mechanism for caching the planned form
+  * of standalone scalar expressions.  While such expressions are not usually
+  * subject to cache invalidation events, that can happen, for example because
+  * of replacement of a SQL function that was inlined into the expression.
+  * The plancache takes care of storing the expression tree and marking it
+  * invalid if a cache invalidation occurs, but the caller must notice the
+  * !is_valid status and discard the obsolete expression without reusing it.
+  * We do not store the original parse tree, only the planned expression;
+  * this is an optimization based on the assumption that we usually will not
+  * need to replan for the life of the session.
+  */
+ typedef struct CachedExpression
+ {
+ 	int			magic;			/* should equal CACHEDEXPR_MAGIC */
+ 	Node	   *expr;			/* planned form of expression */
+ 	bool		is_valid;		/* is the expression still valid? */
+ 	/* remaining fields should be treated as private to plancache.c: */
+ 	List	   *relationOids;	/* OIDs of relations the expr depends on */
+ 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
+ 	MemoryContext context;		/* context containing this CachedExpression */
+ 	dlist_node	node;			/* link in global list of CachedExpressions */
+ } CachedExpression;
+ 
  
  extern void InitPlanCache(void);
  extern void ResetPlanCache(void);
*************** extern CachedPlan *GetCachedPlan(CachedP
*** 182,196 ****
  			  QueryEnvironment *queryEnv);
  extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
  
! /* possible values for plan_cache_mode */
! typedef enum
! {
! 	PLAN_CACHE_MODE_AUTO,
! 	PLAN_CACHE_MODE_FORCE_GENERIC_PLAN,
! 	PLAN_CACHE_MODE_FORCE_CUSTOM_PLAN
! }			PlanCacheMode;
! 
! /* GUC parameter */
! extern int plan_cache_mode;
  
  #endif							/* PLANCACHE_H */
--- 219,225 ----
  			  QueryEnvironment *queryEnv);
  extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
  
! extern CachedExpression *GetCachedExpression(Node *expr);
! extern void FreeCachedExpression(CachedExpression *cexpr);
  
  #endif							/* PLANCACHE_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 39ea925..9078bc1 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** typedef struct					/* cast_hash table en
*** 152,157 ****
--- 152,158 ----
  {
  	plpgsql_CastHashKey key;	/* hash key --- MUST BE FIRST */
  	Expr	   *cast_expr;		/* cast expression, or NULL if no-op cast */
+ 	CachedExpression *cast_cexpr;	/* cached expression backing the above */
  	/* ExprState is valid only when cast_lxid matches current LXID */
  	ExprState  *cast_exprstate; /* expression's eval tree */
  	bool		cast_in_use;	/* true while we're executing eval tree */
*************** get_cast_hashentry(PLpgSQL_execstate *es
*** 7610,7627 ****
  	cast_key.dsttypmod = dsttypmod;
  	cast_entry = (plpgsql_CastHashEntry *) hash_search(estate->cast_hash,
  													   (void *) &cast_key,
! 													   HASH_FIND, NULL);
  
! 	if (cast_entry == NULL)
  	{
! 		/* We've not looked up this coercion before */
  		Node	   *cast_expr;
  		CaseTestExpr *placeholder;
  
  		/*
  		 * Since we could easily fail (no such coercion), construct a
  		 * temporary coercion expression tree in the short-lived
! 		 * eval_mcontext, then if successful copy it to cast_hash_context.
  		 */
  		oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
  
--- 7611,7644 ----
  	cast_key.dsttypmod = dsttypmod;
  	cast_entry = (plpgsql_CastHashEntry *) hash_search(estate->cast_hash,
  													   (void *) &cast_key,
! 													   HASH_ENTER, &found);
! 	if (!found)					/* initialize if new entry */
! 		cast_entry->cast_cexpr = NULL;
  
! 	if (cast_entry->cast_cexpr == NULL ||
! 		!cast_entry->cast_cexpr->is_valid)
  	{
! 		/*
! 		 * We've not looked up this coercion before, or we have but the cached
! 		 * expression has been invalidated.
! 		 */
  		Node	   *cast_expr;
+ 		CachedExpression *cast_cexpr;
  		CaseTestExpr *placeholder;
  
  		/*
+ 		 * Drop old cached expression if there is one.
+ 		 */
+ 		if (cast_entry->cast_cexpr)
+ 		{
+ 			FreeCachedExpression(cast_entry->cast_cexpr);
+ 			cast_entry->cast_cexpr = NULL;
+ 		}
+ 
+ 		/*
  		 * Since we could easily fail (no such coercion), construct a
  		 * temporary coercion expression tree in the short-lived
! 		 * eval_mcontext, then if successful save it as a CachedExpression.
  		 */
  		oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
  
*************** get_cast_hashentry(PLpgSQL_execstate *es
*** 7682,7714 ****
  
  		/* Note: we don't bother labeling the expression tree with collation */
  
  		/* Detect whether we have a no-op (RelabelType) coercion */
  		if (IsA(cast_expr, RelabelType) &&
  			((RelabelType *) cast_expr)->arg == (Expr *) placeholder)
  			cast_expr = NULL;
  
! 		if (cast_expr)
! 		{
! 			/* ExecInitExpr assumes we've planned the expression */
! 			cast_expr = (Node *) expression_planner((Expr *) cast_expr);
! 
! 			/* Now copy the tree into cast_hash_context */
! 			MemoryContextSwitchTo(estate->cast_hash_context);
! 
! 			cast_expr = copyObject(cast_expr);
! 		}
! 
! 		MemoryContextSwitchTo(oldcontext);
! 
! 		/* Now we can fill in a hashtable entry. */
! 		cast_entry = (plpgsql_CastHashEntry *) hash_search(estate->cast_hash,
! 														   (void *) &cast_key,
! 														   HASH_ENTER, &found);
! 		Assert(!found);			/* wasn't there a moment ago */
  		cast_entry->cast_expr = (Expr *) cast_expr;
  		cast_entry->cast_exprstate = NULL;
  		cast_entry->cast_in_use = false;
  		cast_entry->cast_lxid = InvalidLocalTransactionId;
  	}
  
  	/* Done if we have determined that this is a no-op cast. */
--- 7699,7721 ----
  
  		/* Note: we don't bother labeling the expression tree with collation */
  
+ 		/* Plan the expression and build a CachedExpression */
+ 		cast_cexpr = GetCachedExpression(cast_expr);
+ 		cast_expr = cast_cexpr->expr;
+ 
  		/* Detect whether we have a no-op (RelabelType) coercion */
  		if (IsA(cast_expr, RelabelType) &&
  			((RelabelType *) cast_expr)->arg == (Expr *) placeholder)
  			cast_expr = NULL;
  
! 		/* Now we can fill in the hashtable entry. */
! 		cast_entry->cast_cexpr = cast_cexpr;
  		cast_entry->cast_expr = (Expr *) cast_expr;
  		cast_entry->cast_exprstate = NULL;
  		cast_entry->cast_in_use = false;
  		cast_entry->cast_lxid = InvalidLocalTransactionId;
+ 
+ 		MemoryContextSwitchTo(oldcontext);
  	}
  
  	/* Done if we have determined that this is a no-op cast. */
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 0b5a904..11bc772 100644
*** a/src/test/regress/expected/domain.out
--- b/src/test/regress/expected/domain.out
*************** create domain di as int;
*** 1020,1025 ****
--- 1020,1050 ----
  create function dom_check(int) returns di as $$
  declare d di;
  begin
+   d := $1::di;
+   return d;
+ end
+ $$ language plpgsql immutable;
+ select dom_check(0);
+  dom_check 
+ -----------
+          0
+ (1 row)
+ 
+ alter domain di add constraint pos check (value > 0);
+ select dom_check(0); -- fail
+ ERROR:  value for domain di violates check constraint "pos"
+ CONTEXT:  PL/pgSQL function dom_check(integer) line 4 at assignment
+ alter domain di drop constraint pos;
+ select dom_check(0);
+  dom_check 
+ -----------
+          0
+ (1 row)
+ 
+ -- implicit cast during assignment is a separate code path, test that too
+ create or replace function dom_check(int) returns di as $$
+ declare d di;
+ begin
    d := $1;
    return d;
  end
diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql
index 68da27d..1291d55 100644
*** a/src/test/regress/sql/domain.sql
--- b/src/test/regress/sql/domain.sql
*************** create domain di as int;
*** 680,685 ****
--- 680,705 ----
  create function dom_check(int) returns di as $$
  declare d di;
  begin
+   d := $1::di;
+   return d;
+ end
+ $$ language plpgsql immutable;
+ 
+ select dom_check(0);
+ 
+ alter domain di add constraint pos check (value > 0);
+ 
+ select dom_check(0); -- fail
+ 
+ alter domain di drop constraint pos;
+ 
+ select dom_check(0);
+ 
+ -- implicit cast during assignment is a separate code path, test that too
+ 
+ create or replace function dom_check(int) returns di as $$
+ declare d di;
+ begin
    d := $1;
    return d;
  end
