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
@@ -2487,12 +2487,129 @@ EXECUTE st5('foo', 1);
   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;
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
@@ -577,6 +577,26 @@ 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;
@@ -584,6 +604,10 @@ 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)
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
@@ -16,7 +16,9 @@
 #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"
@@ -137,6 +139,8 @@ static List *set_returning_clause_references(PlannerInfo *root,
 								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);
 
@@ -174,8 +178,8 @@ static bool extract_query_dependencies_walker(Node *node,
  * 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.
  *
@@ -426,7 +430,13 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	 * 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);
+	}
 }
 
 /*
@@ -2423,22 +2433,55 @@ record_plan_function_dependency(PlannerInfo *root, Oid funcid)
 	 */
 	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
@@ -2510,8 +2553,14 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 			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 */
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index c96a865..4637745 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -27,15 +27,15 @@
  * 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
+ * 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-2016, PostgreSQL Global Development Group
@@ -116,6 +116,9 @@ 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);
@@ -1751,8 +1754,9 @@ PlanCacheRelCallback(Datum arg, Oid relid)
  * 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.
+ * Note that the coding would support use for multiple caches, and currently,
+ * in addition to user-defined functions, FDW-related objects are tracked this
+ * way.
  */
 static void
 PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue)
