diff --git a/contrib/file_fdw/input/file_fdw.source b/contrib/file_fdw/input/file_fdw.source
new file mode 100644
index f7fd28d..063e23c
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
*************** DELETE FROM agg_csv WHERE a = 100;
*** 121,126 ****
--- 121,148 ----
  -- but this should be ignored
  SELECT * FROM agg_csv FOR UPDATE;
  
+ -- simple views on top of the foreign table aren't updatable
+ CREATE VIEW agg_csv_v AS SELECT * FROM agg_csv;
+ 
+ SELECT table_name, is_insertable_into
+   FROM information_schema.tables
+  WHERE table_name LIKE 'agg_csv%'
+  ORDER BY table_name;
+ 
+ SELECT table_name, is_updatable, is_insertable_into
+   FROM information_schema.views
+  WHERE table_name LIKE 'agg_csv%'
+  ORDER BY table_name;
+ 
+ SELECT table_name, column_name, is_updatable
+   FROM information_schema.columns
+  WHERE table_name LIKE 'agg_csv%'
+  ORDER BY table_name, ordinal_position;
+ 
+ INSERT INTO agg_csv_v VALUES(1,2.0);
+ UPDATE agg_csv_v SET a = 1;
+ DELETE FROM agg_csv_v WHERE a = 100;
+ 
  -- privilege tests
  SET ROLE file_fdw_superuser;
  SELECT * FROM agg_text ORDER BY a;
diff --git a/contrib/file_fdw/output/file_fdw.source b/contrib/file_fdw/output/file_fdw.source
new file mode 100644
index 4f90bae..2e015cc
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
*************** SELECT * FROM agg_csv FOR UPDATE;
*** 199,204 ****
--- 199,243 ----
    42 |  324.78
  (3 rows)
  
+ -- simple views on top of the foreign table aren't updatable
+ CREATE VIEW agg_csv_v AS SELECT * FROM agg_csv;
+ SELECT table_name, is_insertable_into
+   FROM information_schema.tables
+  WHERE table_name LIKE 'agg_csv%'
+  ORDER BY table_name;
+  table_name | is_insertable_into 
+ ------------+--------------------
+  agg_csv    | NO
+  agg_csv_v  | NO
+ (2 rows)
+ 
+ SELECT table_name, is_updatable, is_insertable_into
+   FROM information_schema.views
+  WHERE table_name LIKE 'agg_csv%'
+  ORDER BY table_name;
+  table_name | is_updatable | is_insertable_into 
+ ------------+--------------+--------------------
+  agg_csv_v  | NO           | NO
+ (1 row)
+ 
+ SELECT table_name, column_name, is_updatable
+   FROM information_schema.columns
+  WHERE table_name LIKE 'agg_csv%'
+  ORDER BY table_name, ordinal_position;
+  table_name | column_name | is_updatable 
+ ------------+-------------+--------------
+  agg_csv    | a           | NO
+  agg_csv    | b           | NO
+  agg_csv_v  | a           | NO
+  agg_csv_v  | b           | NO
+ (4 rows)
+ 
+ INSERT INTO agg_csv_v VALUES(1,2.0);
+ ERROR:  cannot insert into foreign table "agg_csv"
+ UPDATE agg_csv_v SET a = 1;
+ ERROR:  cannot update foreign table "agg_csv"
+ DELETE FROM agg_csv_v WHERE a = 100;
+ ERROR:  cannot delete from foreign table "agg_csv"
  -- privilege tests
  SET ROLE file_fdw_superuser;
  SELECT * FROM agg_text ORDER BY a;
*************** SET ROLE file_fdw_superuser;
*** 243,255 ****
  -- cleanup
  RESET ROLE;
  DROP EXTENSION file_fdw CASCADE;
! NOTICE:  drop cascades to 8 other objects
  DETAIL:  drop cascades to server file_server
  drop cascades to user mapping for file_fdw_user
  drop cascades to user mapping for file_fdw_superuser
  drop cascades to user mapping for no_priv_user
  drop cascades to foreign table agg_text
  drop cascades to foreign table agg_csv
  drop cascades to foreign table agg_bad
  drop cascades to foreign table text_csv
  DROP ROLE file_fdw_superuser, file_fdw_user, no_priv_user;
--- 282,295 ----
  -- cleanup
  RESET ROLE;
  DROP EXTENSION file_fdw CASCADE;
! NOTICE:  drop cascades to 9 other objects
  DETAIL:  drop cascades to server file_server
  drop cascades to user mapping for file_fdw_user
  drop cascades to user mapping for file_fdw_superuser
  drop cascades to user mapping for no_priv_user
  drop cascades to foreign table agg_text
  drop cascades to foreign table agg_csv
+ drop cascades to view agg_csv_v
  drop cascades to foreign table agg_bad
  drop cascades to foreign table text_csv
  DROP ROLE file_fdw_superuser, file_fdw_user, no_priv_user;
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
new file mode 100644
index cb007cd..4c2c801
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
*************** select c2, count(*) from "S 1"."T 1" whe
*** 2339,2341 ****
--- 2339,2410 ----
   407 |   100
  (13 rows)
  
+ -- Test updatable view on top of writable foreign table
+ CREATE VIEW ft2_v AS SELECT * FROM ft2;
+ SELECT table_name, is_insertable_into
+   FROM information_schema.tables
+  WHERE table_name LIKE 'ft2%'
+  ORDER BY table_name;
+  table_name | is_insertable_into 
+ ------------+--------------------
+  ft2        | NO
+  ft2_v      | YES
+ (2 rows)
+ 
+ SELECT table_name, is_updatable, is_insertable_into
+   FROM information_schema.views
+  WHERE table_name LIKE 'ft2%'
+  ORDER BY table_name;
+  table_name | is_updatable | is_insertable_into 
+ ------------+--------------+--------------------
+  ft2_v      | YES          | YES
+ (1 row)
+ 
+ SELECT table_name, column_name, is_updatable
+   FROM information_schema.columns
+  WHERE table_name LIKE 'ft2%'
+  ORDER BY table_name, ordinal_position;
+  table_name | column_name | is_updatable 
+ ------------+-------------+--------------
+  ft2        | c1          | NO
+  ft2        | c2          | NO
+  ft2        | c3          | NO
+  ft2        | c4          | NO
+  ft2        | c5          | NO
+  ft2        | c6          | NO
+  ft2        | c7          | NO
+  ft2        | c8          | NO
+  ft2_v      | c1          | YES
+  ft2_v      | c2          | YES
+  ft2_v      | c3          | YES
+  ft2_v      | c4          | YES
+  ft2_v      | c5          | YES
+  ft2_v      | c6          | YES
+  ft2_v      | c7          | YES
+  ft2_v      | c8          | YES
+ (16 rows)
+ 
+ INSERT INTO ft2_v (c1,c2,c3) VALUES (9999, 250, 'xxx') RETURNING *;
+   c1  | c2  |       c3        | c4 | c5 | c6 |     c7     | c8 
+ ------+-----+-----------------+----+----+----+------------+----
+  9999 | 250 | xxx_trig_update |    |    |    | ft2        | 
+ (1 row)
+ 
+ UPDATE ft2_v SET c2 = c2 + 500 WHERE c1 = 9999 RETURNING *;
+   c1  | c2  |             c3              | c4 | c5 | c6 |     c7     | c8 
+ ------+-----+-----------------------------+----+----+----+------------+----
+  9999 | 750 | xxx_trig_update_trig_update |    |    |    | ft2        | 
+ (1 row)
+ 
+ SELECT * FROM ft2_v WHERE c1 = 9999;
+   c1  | c2  |             c3              | c4 | c5 | c6 |     c7     | c8 
+ ------+-----+-----------------------------+----+----+----+------------+----
+  9999 | 750 | xxx_trig_update_trig_update |    |    |    | ft2        | 
+ (1 row)
+ 
+ DELETE FROM ft2_v WHERE c1 = 9999 RETURNING *;
+   c1  | c2  |             c3              | c4 | c5 | c6 |     c7     | c8 
+ ------+-----+-----------------------------+----+----+----+------------+----
+  9999 | 750 | xxx_trig_update_trig_update |    |    |    | ft2        | 
+ (1 row)
+ 
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
new file mode 100644
index 670d769..416afc3
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
*************** select c2, count(*) from "S 1"."T 1" whe
*** 369,371 ****
--- 369,394 ----
  commit;
  select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
  select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
+ 
+ -- Test updatable view on top of writable foreign table
+ CREATE VIEW ft2_v AS SELECT * FROM ft2;
+ 
+ SELECT table_name, is_insertable_into
+   FROM information_schema.tables
+  WHERE table_name LIKE 'ft2%'
+  ORDER BY table_name;
+ 
+ SELECT table_name, is_updatable, is_insertable_into
+   FROM information_schema.views
+  WHERE table_name LIKE 'ft2%'
+  ORDER BY table_name;
+ 
+ SELECT table_name, column_name, is_updatable
+   FROM information_schema.columns
+  WHERE table_name LIKE 'ft2%'
+  ORDER BY table_name, ordinal_position;
+ 
+ INSERT INTO ft2_v (c1,c2,c3) VALUES (9999, 250, 'xxx') RETURNING *;
+ UPDATE ft2_v SET c2 = c2 + 500 WHERE c1 = 9999 RETURNING *;
+ SELECT * FROM ft2_v WHERE c1 = 9999;
+ DELETE FROM ft2_v WHERE c1 = 9999 RETURNING *;
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index 83f26e3..296d6a1
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** view_is_auto_updatable(Relation view)
*** 2014,2019 ****
--- 2014,2020 ----
  	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
  	if (base_rte->rtekind != RTE_RELATION ||
  		(base_rte->relkind != RELKIND_RELATION &&
+ 		 base_rte->relkind != RELKIND_FOREIGN_TABLE &&
  		 base_rte->relkind != RELKIND_VIEW))
  		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
  
*************** relation_is_updatable(Oid reloid, int re
*** 2120,2125 ****
--- 2121,2143 ----
  		}
  	}
  
+ 	/* If this is a foreign table, check if it is updatable */
+ 	if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ 	{
+ 		int			events = 0;
+ 		FdwRoutine *fdwroutine = GetFdwRoutineForRelation(rel, false);
+ 
+ 		if (fdwroutine->ExecForeignInsert != NULL)
+ 			events |= (1 << CMD_INSERT);
+ 		if (fdwroutine->ExecForeignUpdate != NULL)
+ 			events |= (1 << CMD_UPDATE);
+ 		if (fdwroutine->ExecForeignDelete != NULL)
+ 			events |= (1 << CMD_DELETE);
+ 
+ 		relation_close(rel, AccessShareLock);
+ 		return (events & req_events) == req_events;
+ 	}
+ 
  	/* Check if this is an automatically updatable view */
  	if (rel->rd_rel->relkind == RELKIND_VIEW &&
  		view_is_auto_updatable(rel) == NULL)
