diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index d97e694..568671e 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2480,26 +2480,143 @@ EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
    Filter: (t1.c8 = $1)
    Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer))
 (4 rows)
 
 EXECUTE st5('foo', 1);
  c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
 ----+----+-------+------------------------------+--------------------------+----+------------+-----
   1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
 (1 row)
 
+-- changing foreign table options should change the plans
+PREPARE st6 AS SELECT * FROM ft4;
+PREPARE st7 AS INSERT INTO ft4 VALUES (101, 102, 'foo');
+PREPARE st8 AS UPDATE ft4 SET c1 = c1;
+PREPARE st9 AS UPDATE ft4 SET c1 = random();
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
+                    QUERY PLAN                    
+--------------------------------------------------
+ Foreign Scan on public.ft4
+   Output: c1, c2, c3
+   Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3"
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ Insert on public.ft4
+   Remote SQL: INSERT INTO "S 1"."T 3"(c1, c2, c3) VALUES ($1, $2, $3)
+   ->  Result
+         Output: 101, 102, 'foo'::text
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Update on public.ft4
+   ->  Foreign Update on public.ft4
+         Remote SQL: UPDATE "S 1"."T 3" SET c1 = c1
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Update on public.ft4
+   Remote SQL: UPDATE "S 1"."T 3" SET c1 = $2 WHERE ctid = $1
+   ->  Foreign Scan on public.ft4
+         Output: random(), c2, c3, ctid
+         Remote SQL: SELECT c2, c3, ctid FROM "S 1"."T 3" FOR UPDATE
+(5 rows)
+
+ALTER FOREIGN TABLE ft4 OPTIONS (SET table_name 'T 4');
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
+                    QUERY PLAN                    
+--------------------------------------------------
+ Foreign Scan on public.ft4
+   Output: c1, c2, c3
+   Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4"
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ Insert on public.ft4
+   Remote SQL: INSERT INTO "S 1"."T 4"(c1, c2, c3) VALUES ($1, $2, $3)
+   ->  Result
+         Output: 101, 102, 'foo'::text
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Update on public.ft4
+   ->  Foreign Update on public.ft4
+         Remote SQL: UPDATE "S 1"."T 4" SET c1 = c1
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Update on public.ft4
+   Remote SQL: UPDATE "S 1"."T 4" SET c1 = $2 WHERE ctid = $1
+   ->  Foreign Scan on public.ft4
+         Output: random(), c2, c3, ctid
+         Remote SQL: SELECT c2, c3, ctid FROM "S 1"."T 4" FOR UPDATE
+(5 rows)
+
+-- restore the original options and check again
+ALTER FOREIGN TABLE ft4 OPTIONS (SET table_name 'T 3');
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
+                    QUERY PLAN                    
+--------------------------------------------------
+ Foreign Scan on public.ft4
+   Output: c1, c2, c3
+   Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3"
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ Insert on public.ft4
+   Remote SQL: INSERT INTO "S 1"."T 3"(c1, c2, c3) VALUES ($1, $2, $3)
+   ->  Result
+         Output: 101, 102, 'foo'::text
+(4 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8;
+                     QUERY PLAN                     
+----------------------------------------------------
+ Update on public.ft4
+   ->  Foreign Update on public.ft4
+         Remote SQL: UPDATE "S 1"."T 3" SET c1 = c1
+(3 rows)
+
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9;
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Update on public.ft4
+   Remote SQL: UPDATE "S 1"."T 3" SET c1 = $2 WHERE ctid = $1
+   ->  Foreign Scan on public.ft4
+         Output: random(), c2, c3, ctid
+         Remote SQL: SELECT c2, c3, ctid FROM "S 1"."T 3" FOR UPDATE
+(5 rows)
+
 -- cleanup
 DEALLOCATE st1;
 DEALLOCATE st2;
 DEALLOCATE st3;
 DEALLOCATE st4;
 DEALLOCATE st5;
+DEALLOCATE st6;
+DEALLOCATE st7;
+DEALLOCATE st8;
+DEALLOCATE st9;
 -- System columns, except ctid and oid, should not be sent to remote
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1;
                                   QUERY PLAN                                   
 -------------------------------------------------------------------------------
  Limit
    Output: c1, c2, c3, c4, c5, c6, c7, c8
    ->  Foreign Scan on public.ft1 t1
          Output: c1, c2, c3, c4, c5, c6, c7, c8
          Filter: (t1.tableoid = '1259'::oid)
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 4f68e89..f7eca2d 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -570,27 +570,51 @@ EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1);
 EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1);
 -- value of $1 should not be sent to remote
 PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2;
 EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
 EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
 EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
 EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
 EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
 EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
 EXECUTE st5('foo', 1);
+-- changing foreign table options should change the plans
+PREPARE st6 AS SELECT * FROM ft4;
+PREPARE st7 AS INSERT INTO ft4 VALUES (101, 102, 'foo');
+PREPARE st8 AS UPDATE ft4 SET c1 = c1;
+PREPARE st9 AS UPDATE ft4 SET c1 = random();
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9;
+ALTER FOREIGN TABLE ft4 OPTIONS (SET table_name 'T 4');
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9;
+-- restore the original options and check again
+ALTER FOREIGN TABLE ft4 OPTIONS (SET table_name 'T 3');
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8;
+EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st9;
 
 -- cleanup
 DEALLOCATE st1;
 DEALLOCATE st2;
 DEALLOCATE st3;
 DEALLOCATE st4;
 DEALLOCATE st5;
+DEALLOCATE st6;
+DEALLOCATE st7;
+DEALLOCATE st8;
+DEALLOCATE st9;
 
 -- System columns, except ctid and oid, should not be sent to remote
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1;
 SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIMIT 1;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1;
 SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)';
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index d10a983..2f6b4a0 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -9,21 +9,23 @@
  *
  *
  * IDENTIFICATION
  *	  src/backend/optimizer/plan/setrefs.c
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
 #include "access/transam.h"
+#include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
+#include "foreign/foreign.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/tlist.h"
 #include "tcop/utility.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
@@ -130,20 +132,22 @@ static Node *fix_upper_expr(PlannerInfo *root,
 			   indexed_tlist *subplan_itlist,
 			   Index newvarno,
 			   int rtoffset);
 static Node *fix_upper_expr_mutator(Node *node,
 					   fix_upper_expr_context *context);
 static List *set_returning_clause_references(PlannerInfo *root,
 								List *rlist,
 								Plan *topplan,
 								Index resultRelation,
 								int rtoffset);
+static void record_plan_foreign_dependencies(PlannerGlobal *glob, Oid relid);
+static void add_inval_item(PlannerGlobal *glob, int cacheid, Oid oid);
 static bool extract_query_dependencies_walker(Node *node,
 								  PlannerInfo *context);
 
 /*****************************************************************************
  *
  *		SUBPLAN REFERENCES
  *
  *****************************************************************************/
 
 /*
@@ -167,22 +171,22 @@ static bool extract_query_dependencies_walker(Node *node,
  * 5. PARAM_MULTIEXPR Params are replaced by regular PARAM_EXEC Params,
  * now that we have finished planning all MULTIEXPR subplans.
  *
  * 6. We compute regproc OIDs for operators (ie, we look up the function
  * that implements each op).
  *
  * 7. We create lists of specific objects that the plan depends on.
  * 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.
+ * Currently, relations, user-defined functions and FDW-related objects are
+ * the only types of objects that are explicitly tracked this way.
  *
  * 8. We assign every plan node in the tree a unique ID.
  *
  * We also perform one final optimization step, which is to delete
  * SubqueryScan plan nodes that aren't doing anything useful (ie, have
  * no qual and a no-op targetlist).  The reason for doing this last is that
  * it can't readily be done before set_plan_references, because it would
  * break set_upper_references: the Vars in the subquery's top tlist
  * wouldn't match up with the Vars in the outer plan tree.  The SubqueryScan
  * serves a necessary function as a buffer between outer query and subquery
@@ -419,21 +423,27 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	 *
 	 * We do this even though the RTE might be unreferenced in the plan tree;
 	 * this would correspond to cases such as views that were expanded, child
 	 * tables that were eliminated by constraint exclusion, etc. Schema
 	 * invalidation on such a rel must still force rebuilding of the plan.
 	 *
 	 * Note we don't bother to avoid making duplicate list entries.  We could,
 	 * but it would probably cost more cycles than it would save.
 	 */
 	if (newrte->rtekind == RTE_RELATION)
+	{
 		glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
+
+		/* Collect dependencies on FDW-related objects too */
+		if (newrte->relkind == RELKIND_FOREIGN_TABLE)
+			record_plan_foreign_dependencies(glob, newrte->relid);
+	}
 }
 
 /*
  * set_plan_refs: recurse through the Plan nodes of a single subquery level
  */
 static Plan *
 set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 {
 	ListCell   *l;
 
@@ -2416,36 +2426,69 @@ record_plan_function_dependency(PlannerInfo *root, 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)
 	{
-		PlanInvalItem *inval_item = makeNode(PlanInvalItem);
-
 		/*
 		 * It would work to use any syscache on pg_proc, but the easiest is
 		 * PROCOID since we already have the function's OID at hand.  Note
 		 * that plancache.c knows we use PROCOID.
 		 */
-		inval_item->cacheId = PROCOID;
-		inval_item->hashValue = GetSysCacheHashValue1(PROCOID,
-												   ObjectIdGetDatum(funcid));
-
-		root->glob->invalItems = lappend(root->glob->invalItems, inval_item);
+		add_inval_item(root->glob, PROCOID, funcid);
 	}
 }
 
 /*
+ * record_plan_foreign_dependencies
+ *		Mark the current plan as depending on FDW-related objects.
+ *
+ * This is required since modifications to attributes of FDW-related objects,
+ * such as FDW options, might require replanning.
+ */
+static void
+record_plan_foreign_dependencies(PlannerGlobal *glob, Oid relid)
+{
+	ForeignTable *table = GetForeignTable(relid);
+	ForeignServer *server = GetForeignServer(table->serverid);
+
+	/* Record the dependency on the foreign table */
+	add_inval_item(glob, FOREIGNTABLEREL, relid);
+
+	/* Likewise for the foreign server */
+	add_inval_item(glob, FOREIGNSERVEROID, table->serverid);
+
+	/* Likewise for the foreign data wrapper */
+	add_inval_item(glob, FOREIGNDATAWRAPPEROID, server->fdwid);
+}
+
+/*
+ * add_inval_item
+ *		Add given dependency to glob->invalItems.
+ */
+static void
+add_inval_item(PlannerGlobal *glob, int cacheid, Oid oid)
+{
+	PlanInvalItem *inval_item = makeNode(PlanInvalItem);
+
+	inval_item->cacheId = cacheid;
+	inval_item->hashValue = GetSysCacheHashValue1(cacheid,
+												  ObjectIdGetDatum(oid));
+
+	glob->invalItems = lappend(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
  *		just as set_plan_references would do.  Also detect whether any
  *		rewrite steps were affected by RLS.
  *
  * This is needed by plancache.c to handle invalidation of cached unplanned
  * queries.
  */
 void
@@ -2503,21 +2546,27 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 		/* Remember if any Query has RLS quals applied by rewriter */
 		if (query->hasRowSecurity)
 			context->glob->dependsOnRole = true;
 
 		/* Collect relation OIDs in this Query's rtable */
 		foreach(lc, query->rtable)
 		{
 			RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
 
 			if (rte->rtekind == RTE_RELATION)
+			{
 				context->glob->relationOids =
 					lappend_oid(context->glob->relationOids, rte->relid);
+
+				/* Collect dependencies on FDW-related objects too */
+				if (rte->relkind == RELKIND_FOREIGN_TABLE)
+					record_plan_foreign_dependencies(context->glob, 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/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index c96a865..962fd17 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -20,29 +20,29 @@
  * or, if RLS is involved, if the user changes or the RLS environment changes.
  *
  * Note that if the sinval was a result of user DDL actions, parse analysis
  * could throw an error, for example if a column referenced by the query is
  * no longer present.  Another possibility is for the query's output tupdesc
  * to change (for instance "SELECT *" might expand differently than before).
  * The creator of a cached plan can specify whether it is allowable for the
  * 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.
+ * Currently, we track exactly the dependencies of plans on relations,
+ * user-defined functions and FDW-related objects.  On the corresponding
+ * relcache 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-2016, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
  *	  src/backend/utils/cache/plancache.c
  *
  *-------------------------------------------------------------------------
  */
@@ -109,20 +109,23 @@ static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
 /*
  * InitPlanCache: initialize module during InitPostgres.
  *
  * All we need to do is hook into inval.c's callback lists.
  */
 void
 InitPlanCache(void)
 {
 	CacheRegisterRelcacheCallback(PlanCacheRelCallback, (Datum) 0);
 	CacheRegisterSyscacheCallback(PROCOID, PlanCacheFuncCallback, (Datum) 0);
+	CacheRegisterSyscacheCallback(FOREIGNTABLEREL, PlanCacheFuncCallback, (Datum) 0);
+	CacheRegisterSyscacheCallback(FOREIGNSERVEROID, PlanCacheFuncCallback, (Datum) 0);
+	CacheRegisterSyscacheCallback(FOREIGNDATAWRAPPEROID, PlanCacheFuncCallback, (Datum) 0);
 	CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0);
 	CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0);
 	CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
 }
 
 /*
  * CreateCachedPlan: initially create a plan cache entry.
  *
  * Creation of a cached plan is divided into two steps, CreateCachedPlan and
  * CompleteCachedPlan.  CreateCachedPlan should be called after running the
