diff --git a/contrib/file_fdw/input/file_fdw.source b/contrib/file_fdw/input/file_fdw.source
index e975bc552e35fb8bbfeb1f24b4cc8429f46eef75..416753dcadaa42dadaafe7ccdcb2b628fa89befa 100644
--- a/contrib/file_fdw/input/file_fdw.source
+++ b/contrib/file_fdw/input/file_fdw.source
@@ -133,7 +133,7 @@ SELECT tableoid::regclass, b FROM agg_csv;
 INSERT INTO agg_csv VALUES(1,2.0);
 UPDATE agg_csv SET a = 1;
 DELETE FROM agg_csv WHERE a = 100;
--- but this should be ignored
+-- but this should be allowed
 SELECT * FROM agg_csv FOR UPDATE;
 
 -- constraint exclusion tests
@@ -148,6 +148,20 @@ EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
 SELECT * FROM agg_csv WHERE a < 0;
 RESET constraint_exclusion;
 
+-- table inheritance tests
+CREATE TABLE agg (a int2, b float4);
+ALTER FOREIGN TABLE agg_csv INHERIT agg;
+SELECT tableoid::regclass, * FROM agg;
+SELECT tableoid::regclass, * FROM agg_csv;
+SELECT tableoid::regclass, * FROM ONLY agg;
+-- updates aren't supported
+UPDATE agg SET a = 1;
+DELETE FROM agg WHERE a = 100;
+-- but this should be allowed
+SELECT tableoid::regclass, * FROM agg FOR UPDATE;
+ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+DROP TABLE agg;
+
 -- 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
index 9afa0868a6e2ffd167d901a5dbac5caec0517fa0..8719694276a494838dee56dafa23da1a71a75347 100644
--- a/contrib/file_fdw/output/file_fdw.source
+++ b/contrib/file_fdw/output/file_fdw.source
@@ -212,7 +212,7 @@ UPDATE agg_csv SET a = 1;
 ERROR:  cannot update foreign table "agg_csv"
 DELETE FROM agg_csv WHERE a = 100;
 ERROR:  cannot delete from foreign table "agg_csv"
--- but this should be ignored
+-- but this should be allowed
 SELECT * FROM agg_csv FOR UPDATE;
   a  |    b    
 -----+---------
@@ -249,6 +249,46 @@ SELECT * FROM agg_csv WHERE a < 0;
 (0 rows)
 
 RESET constraint_exclusion;
+-- table inheritance tests
+CREATE TABLE agg (a int2, b float4);
+ALTER FOREIGN TABLE agg_csv INHERIT agg;
+SELECT tableoid::regclass, * FROM agg;
+ tableoid |  a  |    b    
+----------+-----+---------
+ agg_csv  | 100 |  99.097
+ agg_csv  |   0 | 0.09561
+ agg_csv  |  42 |  324.78
+(3 rows)
+
+SELECT tableoid::regclass, * FROM agg_csv;
+ tableoid |  a  |    b    
+----------+-----+---------
+ agg_csv  | 100 |  99.097
+ agg_csv  |   0 | 0.09561
+ agg_csv  |  42 |  324.78
+(3 rows)
+
+SELECT tableoid::regclass, * FROM ONLY agg;
+ tableoid | a | b 
+----------+---+---
+(0 rows)
+
+-- updates aren't supported
+UPDATE agg SET a = 1;
+ERROR:  cannot update foreign table "agg_csv"
+DELETE FROM agg WHERE a = 100;
+ERROR:  cannot delete from foreign table "agg_csv"
+-- but this should be allowed
+SELECT tableoid::regclass, * FROM agg FOR UPDATE;
+ tableoid |  a  |    b    
+----------+-----+---------
+ agg_csv  | 100 |  99.097
+ agg_csv  |   0 | 0.09561
+ agg_csv  |  42 |  324.78
+(3 rows)
+
+ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
+DROP TABLE agg;
 -- privilege tests
 SET ROLE file_fdw_superuser;
 SELECT * FROM agg_text ORDER BY a;
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 583cce738aaa987bef3bbf6d7ad8b50663741ea3..4207fb34d054a3c81be6dc0dcc55a6b26c94e429 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -3026,6 +3026,386 @@ NOTICE:  NEW: (13,"test triggered !")
  (0,27)
 (1 row)
 
+-- ===================================================================
+-- test inheritance features
+-- ===================================================================
+CREATE TABLE a (aa TEXT);
+CREATE TABLE loct (aa TEXT, bb TEXT);
+CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+  SERVER loopback OPTIONS (table_name 'loct');
+INSERT INTO a(aa) VALUES('aaa');
+INSERT INTO a(aa) VALUES('aaaa');
+INSERT INTO a(aa) VALUES('aaaaa');
+INSERT INTO b(aa) VALUES('bbb');
+INSERT INTO b(aa) VALUES('bbbb');
+INSERT INTO b(aa) VALUES('bbbbb');
+SELECT tableoid::regclass, * FROM a;
+ tableoid |  aa   
+----------+-------
+ a        | aaa
+ a        | aaaa
+ a        | aaaaa
+ b        | bbb
+ b        | bbbb
+ b        | bbbbb
+(6 rows)
+
+SELECT tableoid::regclass, * FROM b;
+ tableoid |  aa   | bb 
+----------+-------+----
+ b        | bbb   | 
+ b        | bbbb  | 
+ b        | bbbbb | 
+(3 rows)
+
+SELECT tableoid::regclass, * FROM ONLY a;
+ tableoid |  aa   
+----------+-------
+ a        | aaa
+ a        | aaaa
+ a        | aaaaa
+(3 rows)
+
+UPDATE a SET aa = 'zzzzzz' WHERE aa LIKE 'aaaa%';
+SELECT tableoid::regclass, * FROM a;
+ tableoid |   aa   
+----------+--------
+ a        | aaa
+ a        | zzzzzz
+ a        | zzzzzz
+ b        | bbb
+ b        | bbbb
+ b        | bbbbb
+(6 rows)
+
+SELECT tableoid::regclass, * FROM b;
+ tableoid |  aa   | bb 
+----------+-------+----
+ b        | bbb   | 
+ b        | bbbb  | 
+ b        | bbbbb | 
+(3 rows)
+
+SELECT tableoid::regclass, * FROM ONLY a;
+ tableoid |   aa   
+----------+--------
+ a        | aaa
+ a        | zzzzzz
+ a        | zzzzzz
+(3 rows)
+
+UPDATE b SET aa = 'new';
+SELECT tableoid::regclass, * FROM a;
+ tableoid |   aa   
+----------+--------
+ a        | aaa
+ a        | zzzzzz
+ a        | zzzzzz
+ b        | new
+ b        | new
+ b        | new
+(6 rows)
+
+SELECT tableoid::regclass, * FROM b;
+ tableoid | aa  | bb 
+----------+-----+----
+ b        | new | 
+ b        | new | 
+ b        | new | 
+(3 rows)
+
+SELECT tableoid::regclass, * FROM ONLY a;
+ tableoid |   aa   
+----------+--------
+ a        | aaa
+ a        | zzzzzz
+ a        | zzzzzz
+(3 rows)
+
+UPDATE a SET aa = 'newtoo';
+SELECT tableoid::regclass, * FROM a;
+ tableoid |   aa   
+----------+--------
+ a        | newtoo
+ a        | newtoo
+ a        | newtoo
+ b        | newtoo
+ b        | newtoo
+ b        | newtoo
+(6 rows)
+
+SELECT tableoid::regclass, * FROM b;
+ tableoid |   aa   | bb 
+----------+--------+----
+ b        | newtoo | 
+ b        | newtoo | 
+ b        | newtoo | 
+(3 rows)
+
+SELECT tableoid::regclass, * FROM ONLY a;
+ tableoid |   aa   
+----------+--------
+ a        | newtoo
+ a        | newtoo
+ a        | newtoo
+(3 rows)
+
+DELETE FROM a;
+SELECT tableoid::regclass, * FROM a;
+ tableoid | aa 
+----------+----
+(0 rows)
+
+SELECT tableoid::regclass, * FROM b;
+ tableoid | aa | bb 
+----------+----+----
+(0 rows)
+
+SELECT tableoid::regclass, * FROM ONLY a;
+ tableoid | aa 
+----------+----
+(0 rows)
+
+DROP TABLE a CASCADE;
+NOTICE:  drop cascades to foreign table b
+DROP TABLE loct;
+-- Check SELECT FOR UPDATE/SHARE with an inherited source table
+create table loct1 (f1 int, f2 int, f3 int);
+create table loct2 (f1 int, f2 int, f3 int);
+create table foo (f1 int, f2 int);
+create foreign table foo2 (f3 int) inherits (foo)
+  server loopback options (table_name 'loct1');
+create table bar (f1 int, f2 int);
+create foreign table bar2 (f3 int) inherits (bar)
+  server loopback options (table_name 'loct2');
+insert into foo values(1,1);
+insert into foo values(3,3);
+insert into foo2 values(2,2,2);
+insert into foo2 values(4,4,4);
+insert into bar values(1,11);
+insert into bar values(2,22);
+insert into bar values(6,66);
+insert into bar2 values(3,33,33);
+insert into bar2 values(4,44,44);
+insert into bar2 values(7,77,77);
+explain (verbose, costs off)
+select * from bar where f1 in (select f1 from foo) for update;
+                                          QUERY PLAN                                          
+----------------------------------------------------------------------------------------------
+ LockRows
+   Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+   ->  Hash Join
+         Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+         Hash Cond: (bar.f1 = foo.f1)
+         ->  Append
+               ->  Seq Scan on public.bar
+                     Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+               ->  Foreign Scan on public.bar2
+                     Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+                     Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+         ->  Hash
+               Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+               ->  HashAggregate
+                     Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+                     Group Key: foo.f1
+                     ->  Append
+                           ->  Seq Scan on public.foo
+                                 Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+                           ->  Foreign Scan on public.foo2
+                                 Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+                                 Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+(22 rows)
+
+select * from bar where f1 in (select f1 from foo) for update;
+ f1 | f2 
+----+----
+  1 | 11
+  2 | 22
+  3 | 33
+  4 | 44
+(4 rows)
+
+explain (verbose, costs off)
+select * from bar where f1 in (select f1 from foo) for share;
+                                          QUERY PLAN                                          
+----------------------------------------------------------------------------------------------
+ LockRows
+   Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+   ->  Hash Join
+         Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*, foo.ctid, foo.tableoid, foo.*
+         Hash Cond: (bar.f1 = foo.f1)
+         ->  Append
+               ->  Seq Scan on public.bar
+                     Output: bar.f1, bar.f2, bar.ctid, bar.tableoid, bar.*
+               ->  Foreign Scan on public.bar2
+                     Output: bar2.f1, bar2.f2, bar2.ctid, bar2.tableoid, bar2.*
+                     Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE
+         ->  Hash
+               Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+               ->  HashAggregate
+                     Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+                     Group Key: foo.f1
+                     ->  Append
+                           ->  Seq Scan on public.foo
+                                 Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+                           ->  Foreign Scan on public.foo2
+                                 Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+                                 Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+(22 rows)
+
+select * from bar where f1 in (select f1 from foo) for share;
+ f1 | f2 
+----+----
+  1 | 11
+  2 | 22
+  3 | 33
+  4 | 44
+(4 rows)
+
+-- Check UPDATE with inherited target and an inherited source table
+explain (verbose, costs off)
+update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+                                         QUERY PLAN                                          
+---------------------------------------------------------------------------------------------
+ Update on public.bar
+   Update on public.bar
+   Foreign Update on public.bar2
+     Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
+   ->  Hash Join
+         Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.tableoid, foo.*
+         Hash Cond: (bar.f1 = foo.f1)
+         ->  Seq Scan on public.bar
+               Output: bar.f1, bar.f2, bar.ctid
+         ->  Hash
+               Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+               ->  HashAggregate
+                     Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+                     Group Key: foo.f1
+                     ->  Append
+                           ->  Seq Scan on public.foo
+                                 Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+                           ->  Foreign Scan on public.foo2
+                                 Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+                                 Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+   ->  Hash Join
+         Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.tableoid, foo.*
+         Hash Cond: (bar2.f1 = foo.f1)
+         ->  Foreign Scan on public.bar2
+               Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+               Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+         ->  Hash
+               Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+               ->  HashAggregate
+                     Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+                     Group Key: foo.f1
+                     ->  Append
+                           ->  Seq Scan on public.foo
+                                 Output: foo.ctid, foo.tableoid, foo.*, foo.f1
+                           ->  Foreign Scan on public.foo2
+                                 Output: foo2.ctid, foo2.tableoid, foo2.*, foo2.f1
+                                 Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
+(37 rows)
+
+update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+select tableoid::regclass, * from bar order by 1,2;
+ tableoid | f1 | f2  
+----------+----+-----
+ bar      |  1 | 111
+ bar      |  2 | 122
+ bar      |  6 |  66
+ bar2     |  3 | 133
+ bar2     |  4 | 144
+ bar2     |  7 |  77
+(6 rows)
+
+-- Check UPDATE with inherited target and an appendrel subquery
+explain (verbose, costs off)
+update bar set f2 = f2 + 100
+from
+  ( select f1 from foo union all select f1+3 from foo ) ss
+where bar.f1 = ss.f1;
+                                      QUERY PLAN                                      
+--------------------------------------------------------------------------------------
+ Update on public.bar
+   Update on public.bar
+   Foreign Update on public.bar2
+     Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
+   ->  Hash Join
+         Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
+         Hash Cond: (foo.f1 = bar.f1)
+         ->  Append
+               ->  Seq Scan on public.foo
+                     Output: ROW(foo.f1), foo.f1
+               ->  Foreign Scan on public.foo2
+                     Output: ROW(foo2.f1), foo2.f1
+                     Remote SQL: SELECT f1 FROM public.loct1
+               ->  Seq Scan on public.foo foo_1
+                     Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+               ->  Foreign Scan on public.foo2 foo2_1
+                     Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+                     Remote SQL: SELECT f1 FROM public.loct1
+         ->  Hash
+               Output: bar.f1, bar.f2, bar.ctid
+               ->  Seq Scan on public.bar
+                     Output: bar.f1, bar.f2, bar.ctid
+   ->  Merge Join
+         Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1))
+         Merge Cond: (bar2.f1 = foo.f1)
+         ->  Sort
+               Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+               Sort Key: bar2.f1
+               ->  Foreign Scan on public.bar2
+                     Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
+                     Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+         ->  Sort
+               Output: (ROW(foo.f1)), foo.f1
+               Sort Key: foo.f1
+               ->  Append
+                     ->  Seq Scan on public.foo
+                           Output: ROW(foo.f1), foo.f1
+                     ->  Foreign Scan on public.foo2
+                           Output: ROW(foo2.f1), foo2.f1
+                           Remote SQL: SELECT f1 FROM public.loct1
+                     ->  Seq Scan on public.foo foo_1
+                           Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
+                     ->  Foreign Scan on public.foo2 foo2_1
+                           Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
+                           Remote SQL: SELECT f1 FROM public.loct1
+(45 rows)
+
+update bar set f2 = f2 + 100
+from
+  ( select f1 from foo union all select f1+3 from foo ) ss
+where bar.f1 = ss.f1;
+select tableoid::regclass, * from bar order by 1,2;
+ tableoid | f1 | f2  
+----------+----+-----
+ bar      |  1 | 211
+ bar      |  2 | 222
+ bar      |  6 | 166
+ bar2     |  3 | 233
+ bar2     |  4 | 244
+ bar2     |  7 | 177
+(6 rows)
+
+-- Test that WHERE CURRENT OF is not supported
+begin;
+declare c cursor for select * from bar where f1 = 7;
+fetch from c;
+ f1 | f2  
+----+-----
+  7 | 177
+(1 row)
+
+update bar set f2 = null where current of c;
+ERROR:  WHERE CURRENT OF is not supported for this table type
+rollback;
+drop table foo cascade;
+NOTICE:  drop cascades to foreign table foo2
+drop table bar cascade;
+NOTICE:  drop cascades to foreign table bar2
+drop table loct1;
+drop table loct2;
 -- ===================================================================
 -- test IMPORT FOREIGN SCHEMA
 -- ===================================================================
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 83e8fa7b045f93481a20f07055d1f6f6224cbdcb..4a23457e796536abce8b3c35cec59cc7ebd5962a 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -665,6 +665,116 @@ UPDATE rem1 SET f2 = 'testo';
 -- Test returning a system attribute
 INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
 
+-- ===================================================================
+-- test inheritance features
+-- ===================================================================
+
+CREATE TABLE a (aa TEXT);
+CREATE TABLE loct (aa TEXT, bb TEXT);
+CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
+  SERVER loopback OPTIONS (table_name 'loct');
+
+INSERT INTO a(aa) VALUES('aaa');
+INSERT INTO a(aa) VALUES('aaaa');
+INSERT INTO a(aa) VALUES('aaaaa');
+
+INSERT INTO b(aa) VALUES('bbb');
+INSERT INTO b(aa) VALUES('bbbb');
+INSERT INTO b(aa) VALUES('bbbbb');
+
+SELECT tableoid::regclass, * FROM a;
+SELECT tableoid::regclass, * FROM b;
+SELECT tableoid::regclass, * FROM ONLY a;
+
+UPDATE a SET aa = 'zzzzzz' WHERE aa LIKE 'aaaa%';
+
+SELECT tableoid::regclass, * FROM a;
+SELECT tableoid::regclass, * FROM b;
+SELECT tableoid::regclass, * FROM ONLY a;
+
+UPDATE b SET aa = 'new';
+
+SELECT tableoid::regclass, * FROM a;
+SELECT tableoid::regclass, * FROM b;
+SELECT tableoid::regclass, * FROM ONLY a;
+
+UPDATE a SET aa = 'newtoo';
+
+SELECT tableoid::regclass, * FROM a;
+SELECT tableoid::regclass, * FROM b;
+SELECT tableoid::regclass, * FROM ONLY a;
+
+DELETE FROM a;
+
+SELECT tableoid::regclass, * FROM a;
+SELECT tableoid::regclass, * FROM b;
+SELECT tableoid::regclass, * FROM ONLY a;
+
+DROP TABLE a CASCADE;
+DROP TABLE loct;
+
+-- Check SELECT FOR UPDATE/SHARE with an inherited source table
+create table loct1 (f1 int, f2 int, f3 int);
+create table loct2 (f1 int, f2 int, f3 int);
+
+create table foo (f1 int, f2 int);
+create foreign table foo2 (f3 int) inherits (foo)
+  server loopback options (table_name 'loct1');
+create table bar (f1 int, f2 int);
+create foreign table bar2 (f3 int) inherits (bar)
+  server loopback options (table_name 'loct2');
+
+insert into foo values(1,1);
+insert into foo values(3,3);
+insert into foo2 values(2,2,2);
+insert into foo2 values(4,4,4);
+insert into bar values(1,11);
+insert into bar values(2,22);
+insert into bar values(6,66);
+insert into bar2 values(3,33,33);
+insert into bar2 values(4,44,44);
+insert into bar2 values(7,77,77);
+
+explain (verbose, costs off)
+select * from bar where f1 in (select f1 from foo) for update;
+select * from bar where f1 in (select f1 from foo) for update;
+
+explain (verbose, costs off)
+select * from bar where f1 in (select f1 from foo) for share;
+select * from bar where f1 in (select f1 from foo) for share;
+
+-- Check UPDATE with inherited target and an inherited source table
+explain (verbose, costs off)
+update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
+
+select tableoid::regclass, * from bar order by 1,2;
+
+-- Check UPDATE with inherited target and an appendrel subquery
+explain (verbose, costs off)
+update bar set f2 = f2 + 100
+from
+  ( select f1 from foo union all select f1+3 from foo ) ss
+where bar.f1 = ss.f1;
+update bar set f2 = f2 + 100
+from
+  ( select f1 from foo union all select f1+3 from foo ) ss
+where bar.f1 = ss.f1;
+
+select tableoid::regclass, * from bar order by 1,2;
+
+-- Test that WHERE CURRENT OF is not supported
+begin;
+declare c cursor for select * from bar where f1 = 7;
+fetch from c;
+update bar set f2 = null where current of c;
+rollback;
+
+drop table foo cascade;
+drop table bar cascade;
+drop table loct1;
+drop table loct2;
+
 -- ===================================================================
 -- test IMPORT FOREIGN SCHEMA
 -- ===================================================================
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 570a003e4a9d37daa470dbe007342f8a824bad6c..1c56f162de3f9c338d5fc2b151e400a450db02ef 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -546,7 +546,7 @@ CREATE TABLE products (
 
    <para>
     Adding a unique constraint will automatically create a unique btree
-    index on the column or group of columns used in the constraint. 
+    index on the column or group of columns used in the constraint.
     A uniqueness constraint on only some rows can be enforced by creating
     a <link linkend="indexes-partial">partial index</link>.
    </para>
@@ -2408,6 +2408,17 @@ WHERE c.altitude &gt; 500 AND c.tableoid = p.oid;
 </programlisting>
   </para>
 
+  <para>
+   Another way to get the same effect is to use the <type>regclass</>
+   pseudo-type, which will print the table OID symbolically:
+
+<programlisting>
+SELECT c.tableoid::regclass, c.name, c.altitude
+FROM cities c
+WHERE c.altitude &gt; 500;
+</programlisting>
+  </para>
+
   <para>
    Inheritance does not automatically propagate data from
    <command>INSERT</command> or <command>COPY</command> commands to
@@ -2503,6 +2514,14 @@ VALUES ('Albany', NULL, NULL, 'NY');
    further privileges to be granted.
   </para>
 
+  <para>
+   Foreign tables (see <xref linkend="ddl-foreign-data">) can also
+   be part of inheritance hierarchies, either as parent or child
+   tables, just as regular tables can be.  If a foreign table is part
+   of an inheritance hierarchy then any operations not supported by
+   the foreign table are not supported on the whole hierarchy either.
+  </para>
+
  <sect2 id="ddl-inherit-caveats">
   <title>Caveats</title>
 
@@ -2714,7 +2733,8 @@ VALUES ('Albany', NULL, NULL, 'NY');
 
        <para>
         We will refer to the child tables as partitions, though they
-        are in every way normal <productname>PostgreSQL</> tables.
+        are in every way normal <productname>PostgreSQL</> tables
+        (or, possibly, foreign tables).
        </para>
       </listitem>
 
diff --git a/doc/src/sgml/perform.sgml b/doc/src/sgml/perform.sgml
index c73580ed460c77806203dffd3ad7fb22b66597f7..7bcbfa76112388a9703272cfe604d56fc1508799 100644
--- a/doc/src/sgml/perform.sgml
+++ b/doc/src/sgml/perform.sgml
@@ -761,6 +761,38 @@ ROLLBACK;
     decisions.
    </para>
 
+   <para>
+    When an <command>UPDATE</> or <command>DELETE</> command affects an
+    inheritance hierarchy, the output might look like this:
+
+<screen>
+EXPLAIN UPDATE parent SET f2 = f2 + 1 WHERE f1 = 101;
+                                    QUERY PLAN
+-----------------------------------------------------------------------------------
+ Update on parent  (cost=0.00..24.53 rows=4 width=14)
+   Update on parent
+   Update on child1
+   Update on child2
+   Update on child3
+   -&gt;  Seq Scan on parent  (cost=0.00..0.00 rows=1 width=14)
+         Filter: (f1 = 101)
+   -&gt;  Index Scan using child1_f1_key on child1  (cost=0.15..8.17 rows=1 width=14)
+         Index Cond: (f1 = 101)
+   -&gt;  Index Scan using child2_f1_key on child2  (cost=0.15..8.17 rows=1 width=14)
+         Index Cond: (f1 = 101)
+   -&gt;  Index Scan using child3_f1_key on child3  (cost=0.15..8.17 rows=1 width=14)
+         Index Cond: (f1 = 101)
+</screen>
+
+    In this example the Update node needs to consider three child tables as
+    well as the originally-mentioned parent table.  So there are four input
+    scanning subplans, one per table.  For clarity, the Update node is
+    annotated to show the specific target tables that will be updated, in the
+    same order as the corresponding subplans.  (These annotations are new as
+    of <productname>PostgreSQL</> 9.5; in prior versions the reader had to
+    intuit the target tables by inspecting the subplans.)
+   </para>
+
    <para>
     The <literal>Planning time</literal> shown by <command>EXPLAIN
     ANALYZE</command> is the time it took to generate the query plan from the
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index 93f8743071bf30d2bdd1e586b495d995821e08e2..ace0040a9b7ab3991321cca2b4212030a23ffde3 100644
--- a/doc/src/sgml/ref/alter_foreign_table.sgml
+++ b/doc/src/sgml/ref/alter_foreign_table.sgml
@@ -21,9 +21,9 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
+ALTER FOREIGN TABLE [ IF EXISTS ] [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ]
     <replaceable class="PARAMETER">action</replaceable> [, ... ]
-ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
+ALTER FOREIGN TABLE [ IF EXISTS ] [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ]
     RENAME [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> TO <replaceable class="PARAMETER">new_column_name</replaceable>
 ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     RENAME TO <replaceable class="PARAMETER">new_name</replaceable>
@@ -34,20 +34,26 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
 
     ADD [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ COLLATE <replaceable class="PARAMETER">collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="PARAMETER">column_name</replaceable> [ RESTRICT | CASCADE ]
-    ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="PARAMETER">data_type</replaceable>
+    ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="PARAMETER">data_type</replaceable> [ COLLATE <replaceable class="PARAMETER">collation</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET DEFAULT <replaceable class="PARAMETER">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> DROP DEFAULT
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> { SET | DROP } NOT NULL
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET STATISTICS <replaceable class="PARAMETER">integer</replaceable>
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
+    ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
-    ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+    ADD <replaceable class="PARAMETER">table_constraint</replaceable> [ NOT VALID ]
+    VALIDATE CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable>
     DROP CONSTRAINT [ IF EXISTS ]  <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
     DISABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
     ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
     ENABLE REPLICA TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
     ENABLE ALWAYS TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable>
+    SET WITH OIDS
+    SET WITHOUT OIDS
+    INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+    NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
     OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
     OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
 </synopsis>
@@ -94,6 +100,9 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
     <listitem>
      <para>
       This form changes the type of a column of a foreign table.
+      Again, this has no effect on any underlying storage: this action simply
+      changes the type that <productname>PostgreSQL</> believes the column to
+      have.
      </para>
     </listitem>
    </varlistentry>
@@ -145,7 +154,22 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
    </varlistentry>
 
    <varlistentry>
-    <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+    <term>
+     <literal>SET STORAGE</literal>
+    </term>
+    <listitem>
+     <para>
+      This form sets the storage mode for a column.
+      See the similar form of <xref linkend="sql-altertable">
+      for more details.
+      Note that the storage mode has no effect unless the table's
+      foreign-data wrapper chooses to pay attention to it.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal> [ NOT VALID ]</term>
     <listitem>
      <para>
       This form adds a new constraint to a foreign table, using the same
@@ -156,10 +180,22 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
      <para>
       Unlike the case when adding a constraint to a regular table, nothing is
       done to verify the constraint is correct; rather, this action simply
-      declares that some new condition holds for all rows in the foreign
-      table.  (See the discussion in <xref linkend="SQL-CREATEFOREIGNTABLE">.)
-      Note that constraints on foreign tables cannot be marked
-      <literal>NOT VALID</> since such constraints are simply declarative.
+      declares that some new condition should be assumed to hold for all rows
+      in the foreign table.  (See the discussion
+      in <xref linkend="SQL-CREATEFOREIGNTABLE">.)
+      If the constraint is marked <literal>NOT VALID</>, then it isn't
+      assumed to hold, but is only recorded for possible future use.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>VALIDATE CONSTRAINT</literal></term>
+    <listitem>
+     <para>
+      This form marks as valid a constraint that was previously marked
+      as <literal>NOT VALID</literal>.  No action is taken to verify the
+      constraint, but future queries will assume that it holds.
      </para>
     </listitem>
    </varlistentry>
@@ -187,6 +223,60 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>SET WITH OIDS</literal></term>
+    <listitem>
+     <para>
+      This form adds an <literal>oid</literal> system column to the
+      table (see <xref linkend="ddl-system-columns">).
+      It does nothing if the table already has OIDs.
+      Unless the table's foreign-data wrapper supports OIDs, this column
+      will simply read as zeroes.
+     </para>
+
+     <para>
+      Note that this is not equivalent to <literal>ADD COLUMN oid oid</>;
+      that would add a normal column that happened to be named
+      <literal>oid</>, not a system column.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>SET WITHOUT OIDS</literal></term>
+    <listitem>
+     <para>
+      This form removes the <literal>oid</literal> system column from the
+      table.  This is exactly equivalent to
+      <literal>DROP COLUMN oid RESTRICT</literal>,
+      except that it will not complain if there is already no
+      <literal>oid</literal> column.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+    <listitem>
+     <para>
+      This form adds the target foreign table as a new child of the specified
+      parent table.
+      See the similar form of <xref linkend="sql-altertable">
+      for more details.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+    <listitem>
+     <para>
+      This form removes the target foreign table from the list of children of
+      the specified parent table.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>OWNER</literal></term>
     <listitem>
@@ -272,7 +362,11 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
       <listitem>
        <para>
         The name (possibly schema-qualified) of an existing foreign table to
-        alter.
+        alter. If <literal>ONLY</> is specified before the table name, only
+        that table is altered. If <literal>ONLY</> is not specified, the table
+        and all its descendant tables (if any) are altered.  Optionally,
+        <literal>*</> can be specified after the table name to explicitly
+        indicate that descendant tables are included.
        </para>
       </listitem>
      </varlistentry>
@@ -383,6 +477,15 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+      <listitem>
+       <para>
+        A parent table to associate or de-associate with this foreign table.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="PARAMETER">new_owner</replaceable></term>
       <listitem>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 08d316a9e43a0918742ef95f1655f7ae5f68675b..27ab4fca42c514c1c10d46a5d73c90d161804ac6 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -199,6 +199,12 @@ ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
     run <command>ANALYZE</command> manually.
   </para>
 
+  <para>
+    If any of the child tables are foreign tables whose foreign data wrappers
+    do not support <command>ANALYZE</>, those child tables are ignored while
+    gathering inheritance statistics.
+  </para>
+
   <para>
     If the table being analyzed is completely empty, <command>ANALYZE</command>
     will not record new statistics for that table.  Any existing statistics
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index a24aa6e6d1dc7da0e8ff758c55e7cd5735217f76..abadd83fc386cc969f70919c6a3b48e39f820ad3 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -23,6 +23,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
     | <replaceable>table_constraint</replaceable> }
     [, ... ]
 ] )
+[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
   SERVER <replaceable class="parameter">server_name</replaceable>
 [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
 
@@ -120,6 +121,44 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> )
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>COLLATE <replaceable>collation</replaceable></literal></term>
+    <listitem>
+     <para>
+      The <literal>COLLATE</> clause assigns a collation to
+      the column (which must be of a collatable data type).
+      If not specified, the column data type's default collation is used.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>INHERITS ( <replaceable>parent_table</replaceable> [, ... ] )</literal></term>
+    <listitem>
+     <para>
+      The optional <literal>INHERITS</> clause specifies a list of
+      tables from which the new foreign table automatically inherits
+      all columns.  Parent tables can be plain tables or foreign tables.
+      See the similar form of
+      <xref linkend="sql-createtable"> for more details.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable></literal></term>
+    <listitem>
+     <para>
+      An optional name for a column or table constraint.  If the
+      constraint is violated, the constraint name is present in error messages,
+      so constraint names like <literal>col must be positive</> can be used
+      to communicate helpful constraint information to client applications.
+      (Double-quotes are needed to specify constraint names that contain spaces.)
+      If a constraint name is not specified, the system generates a name.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>NOT NULL</></term>
     <listitem>
@@ -145,7 +184,7 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> )
    </varlistentry>
 
    <varlistentry>
-    <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> )</literal></term>
+    <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] </literal></term>
     <listitem>
      <para>
       The <literal>CHECK</> clause specifies an expression producing a
@@ -163,6 +202,11 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> )
       current row.  The system column <literal>tableoid</literal>
       may be referenced, but not any other system column.
      </para>
+
+     <para>
+      A constraint marked with <literal>NO INHERIT</> will not propagate to
+      child tables.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -280,8 +324,9 @@ SERVER film_server;
    <acronym>SQL</acronym> standard; however, much as with
    <link linkend="sql-createtable"><command>CREATE TABLE</></link>,
    <literal>NULL</> constraints and zero-column foreign tables are permitted.
-   The ability to specify a default value is also a <productname>PostgreSQL</>
-   extension.
+   The ability to specify column default values is also
+   a <productname>PostgreSQL</> extension.  Table inheritance, in the form
+   defined by <productname>PostgreSQL</productname>, is nonstandard.
   </para>
 
  </refsect1>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bc6db457a2c841faa1a4846a809788884c00efb3..3e6246da66867db9081ae1695cdfabdb5da01c86 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -267,7 +267,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
      <para>
       The optional <literal>INHERITS</> clause specifies a list of
       tables from which the new table automatically inherits all
-      columns.
+      columns.  Parent tables can be plain tables or foreign tables.
      </para>
 
      <para>
@@ -294,7 +294,8 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
       error will be reported.
      </para>
 
-     <para><literal>CHECK</> constraints are merged in essentially the same way as
+     <para>
+      <literal>CHECK</> constraints are merged in essentially the same way as
       columns: if multiple parent tables and/or the new table definition
       contain identically-named <literal>CHECK</> constraints, these
       constraints must all have the same check expression, or an error will be
diff --git a/doc/src/sgml/ref/truncate.sgml b/doc/src/sgml/ref/truncate.sgml
index 87516c9cc365dc6054cd9e36b4226d37c433fa6e..10c4fdd359abbee3622edd123f4e3fcd7f53a267 100644
--- a/doc/src/sgml/ref/truncate.sgml
+++ b/doc/src/sgml/ref/truncate.sgml
@@ -179,6 +179,12 @@ TRUNCATE [ TABLE ] [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [
    This is similar to the usual behavior of <function>currval()</> after
    a failed transaction.
   </para>
+
+  <para>
+   <command>TRUNCATE</> is not currently supported for foreign tables.
+   This implies that if a specified table has any descendant tables that are
+   foreign, the command will fail.
+  </para>
  </refsect1>
 
  <refsect1>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5ce43957d9fef19def0bbffb5ac6aeecebedc34b..e0dbcea0939f288be30029a97b1954277964236c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2253,13 +2253,6 @@ AddRelationNewConstraints(Relation rel,
 			expr = stringToNode(cdef->cooked_expr);
 		}
 
-		/* Don't allow NOT VALID for foreign tables */
-		if (cdef->skip_validation &&
-			rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("CHECK constraints on foreign tables cannot be marked NOT VALID")));
-
 		/*
 		 * Check name uniqueness, or generate a name if none was given.
 		 */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 75b45f7cd5de4a22823812d072cc09d69a301d17..366c4af27fa0d1bf42b6a04171019b318f9035cb 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -297,9 +297,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options, List *va_cols,
  *	do_analyze_rel() -- analyze one relation, recursively or not
  *
  * Note that "acquirefunc" is only relevant for the non-inherited case.
- * If we supported foreign tables in inheritance trees,
- * acquire_inherited_sample_rows would need to determine the appropriate
- * acquirefunc for each child table.
+ * For the inherited case, acquire_inherited_sample_rows() determines the
+ * appropriate acquirefunc for each child table.
  */
 static void
 do_analyze_rel(Relation onerel, int options, List *va_cols,
@@ -1448,7 +1447,8 @@ compare_rows(const void *a, const void *b)
  *
  * This has the same API as acquire_sample_rows, except that rows are
  * collected from all inheritance children as well as the specified table.
- * We fail and return zero if there are no inheritance children.
+ * We fail and return zero if there are no inheritance children, or if all
+ * children are foreign tables that don't support ANALYZE.
  */
 static int
 acquire_inherited_sample_rows(Relation onerel, int elevel,
@@ -1457,6 +1457,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 {
 	List	   *tableOIDs;
 	Relation   *rels;
+	AcquireSampleRowsFunc *acquirefuncs;
 	double	   *relblocks;
 	double		totalblocks;
 	int			numrows,
@@ -1491,10 +1492,12 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 	}
 
 	/*
-	 * Count the blocks in all the relations.  The result could overflow
-	 * BlockNumber, so we use double arithmetic.
+	 * Identify acquirefuncs to use, and count blocks in all the relations.
+	 * The result could overflow BlockNumber, so we use double arithmetic.
 	 */
 	rels = (Relation *) palloc(list_length(tableOIDs) * sizeof(Relation));
+	acquirefuncs = (AcquireSampleRowsFunc *)
+		palloc(list_length(tableOIDs) * sizeof(AcquireSampleRowsFunc));
 	relblocks = (double *) palloc(list_length(tableOIDs) * sizeof(double));
 	totalblocks = 0;
 	nrels = 0;
@@ -1502,6 +1505,8 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 	{
 		Oid			childOID = lfirst_oid(lc);
 		Relation	childrel;
+		AcquireSampleRowsFunc acquirefunc = NULL;
+		BlockNumber relpages = 0;
 
 		/* We already got the needed lock */
 		childrel = heap_open(childOID, NoLock);
@@ -1515,12 +1520,66 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 			continue;
 		}
 
+		/* Check table type (MATVIEW can't happen, but might as well allow) */
+		if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+			childrel->rd_rel->relkind == RELKIND_MATVIEW)
+		{
+			/* Regular table, so use the regular row acquisition function */
+			acquirefunc = acquire_sample_rows;
+			relpages = RelationGetNumberOfBlocks(childrel);
+		}
+		else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+		{
+			/*
+			 * For a foreign table, call the FDW's hook function to see
+			 * whether it supports analysis.
+			 */
+			FdwRoutine *fdwroutine;
+			bool		ok = false;
+
+			fdwroutine = GetFdwRoutineForRelation(childrel, false);
+
+			if (fdwroutine->AnalyzeForeignTable != NULL)
+				ok = fdwroutine->AnalyzeForeignTable(childrel,
+													 &acquirefunc,
+													 &relpages);
+
+			if (!ok)
+			{
+				/* ignore, but release the lock on it */
+				Assert(childrel != onerel);
+				heap_close(childrel, AccessShareLock);
+				continue;
+			}
+		}
+		else
+		{
+			/* ignore, but release the lock on it */
+			Assert(childrel != onerel);
+			heap_close(childrel, AccessShareLock);
+			continue;
+		}
+
+		/* OK, we'll process this child */
 		rels[nrels] = childrel;
-		relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
-		totalblocks += relblocks[nrels];
+		acquirefuncs[nrels] = acquirefunc;
+		relblocks[nrels] = (double) relpages;
+		totalblocks += (double) relpages;
 		nrels++;
 	}
 
+	/*
+	 * If we don't have at least two tables to consider, fail.
+	 */
+	if (nrels < 2)
+	{
+		ereport(elevel,
+				(errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains no analyzable child tables",
+						get_namespace_name(RelationGetNamespace(onerel)),
+						RelationGetRelationName(onerel))));
+		return 0;
+	}
+
 	/*
 	 * Now sample rows from each relation, proportionally to its fraction of
 	 * the total block count.  (This might be less than desirable if the child
@@ -1533,6 +1592,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 	for (i = 0; i < nrels; i++)
 	{
 		Relation	childrel = rels[i];
+		AcquireSampleRowsFunc acquirefunc = acquirefuncs[i];
 		double		childblocks = relblocks[i];
 
 		if (childblocks > 0)
@@ -1549,12 +1609,9 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
 							tdrows;
 
 				/* Fetch a random sample of the child's rows */
-				childrows = acquire_sample_rows(childrel,
-												elevel,
-												rows + numrows,
-												childtargrows,
-												&trows,
-												&tdrows);
+				childrows = (*acquirefunc) (childrel, elevel,
+											rows + numrows, childtargrows,
+											&trows, &tdrows);
 
 				/* We may need to convert from child's rowtype to parent's */
 				if (childrows > 0 &&
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index a951c55ed3468da75f7707168583eae4e45ebeb4..315a52849c9494afd3d554639e85b7f765a4d28a 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2187,6 +2187,10 @@ ExplainScanTarget(Scan *plan, ExplainState *es)
 
 /*
  * Show the target of a ModifyTable node
+ *
+ * Here we show the nominal target (ie, the relation that was named in the
+ * original query).  If the actual target(s) is/are different, we'll show them
+ * in show_modifytable_info().
  */
 static void
 ExplainModifyTarget(ModifyTable *plan, ExplainState *es)
@@ -2303,30 +2307,106 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 
 /*
  * Show extra information for a ModifyTable node
+ *
+ * We have two objectives here.  First, if there's more than one target table
+ * or it's different from the nominal target, identify the actual target(s).
+ * Second, give FDWs a chance to display extra info about foreign targets.
  */
 static void
 show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
 {
-	FdwRoutine *fdwroutine = mtstate->resultRelInfo->ri_FdwRoutine;
+	ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+	const char *operation;
+	const char *foperation;
+	bool		labeltargets;
+	int			j;
 
-	/*
-	 * If the first target relation is a foreign table, call its FDW to
-	 * display whatever additional fields it wants to.  For now, we ignore the
-	 * possibility of other targets being foreign tables, although the API for
-	 * ExplainForeignModify is designed to allow them to be processed.
-	 */
-	if (fdwroutine != NULL &&
-		fdwroutine->ExplainForeignModify != NULL)
+	switch (node->operation)
 	{
-		ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
-		List	   *fdw_private = (List *) linitial(node->fdwPrivLists);
-
-		fdwroutine->ExplainForeignModify(mtstate,
-										 mtstate->resultRelInfo,
-										 fdw_private,
-										 0,
-										 es);
+		case CMD_INSERT:
+			operation = "Insert";
+			foperation = "Foreign Insert";
+			break;
+		case CMD_UPDATE:
+			operation = "Update";
+			foperation = "Foreign Update";
+			break;
+		case CMD_DELETE:
+			operation = "Delete";
+			foperation = "Foreign Delete";
+			break;
+		default:
+			operation = "???";
+			foperation = "Foreign ???";
+			break;
+	}
+
+	/* Should we explicitly label target relations? */
+	labeltargets = (mtstate->mt_nplans > 1 ||
+					(mtstate->mt_nplans == 1 &&
+	   mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+
+	if (labeltargets)
+		ExplainOpenGroup("Target Tables", "Target Tables", false, es);
+
+	for (j = 0; j < mtstate->mt_nplans; j++)
+	{
+		ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
+		FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
+
+		if (labeltargets)
+		{
+			/* Open a group for this target */
+			ExplainOpenGroup("Target Table", NULL, true, es);
+
+			/*
+			 * In text mode, decorate each target with operation type, so that
+			 * ExplainTargetRel's output of " on foo" will read nicely.
+			 */
+			if (es->format == EXPLAIN_FORMAT_TEXT)
+			{
+				appendStringInfoSpaces(es->str, es->indent * 2);
+				appendStringInfoString(es->str,
+									   fdwroutine ? foperation : operation);
+			}
+
+			/* Identify target */
+			ExplainTargetRel((Plan *) node,
+							 resultRelInfo->ri_RangeTableIndex,
+							 es);
+
+			if (es->format == EXPLAIN_FORMAT_TEXT)
+			{
+				appendStringInfoChar(es->str, '\n');
+				es->indent++;
+			}
+		}
+
+		/* Give FDW a chance */
+		if (fdwroutine && fdwroutine->ExplainForeignModify != NULL)
+		{
+			List	   *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
+
+			fdwroutine->ExplainForeignModify(mtstate,
+											 resultRelInfo,
+											 fdw_private,
+											 j,
+											 es);
+		}
+
+		if (labeltargets)
+		{
+			/* Undo the indentation we added in text format */
+			if (es->format == EXPLAIN_FORMAT_TEXT)
+				es->indent--;
+
+			/* Close the group */
+			ExplainCloseGroup("Target Table", NULL, true, es);
+		}
 	}
+
+	if (labeltargets)
+		ExplainCloseGroup("Target Tables", "Target Tables", false, es);
 }
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4147457505519aad50f30ed05fa54fee49d72a0d..a9f79431e5c1f70ca2d8c55766e8d2e961ec0965 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1504,10 +1504,11 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 */
 		relation = heap_openrv(parent, ShareUpdateExclusiveLock);
 
-		if (relation->rd_rel->relkind != RELKIND_RELATION)
+		if (relation->rd_rel->relkind != RELKIND_RELATION &&
+			relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("inherited relation \"%s\" is not a table",
+					 errmsg("inherited relation \"%s\" is not a table or foreign table",
 							parent->relname)));
 		/* Permanent rels cannot inherit from temporary ones */
 		if (relpersistence != RELPERSISTENCE_TEMP &&
@@ -3157,7 +3158,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			pass = AT_PASS_MISC;
 			break;
 		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
-			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
+			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
@@ -3245,14 +3246,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			pass = AT_PASS_MISC;
 			break;
 		case AT_AddOids:		/* SET WITH OIDS */
-			ATSimplePermissions(rel, ATT_TABLE);
+			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 			if (!rel->rd_rel->relhasoids || recursing)
 				ATPrepAddOids(wqueue, rel, recurse, cmd, lockmode);
 			/* Recursion occurs during execution phase */
 			pass = AT_PASS_ADD_COL;
 			break;
 		case AT_DropOids:		/* SET WITHOUT OIDS */
-			ATSimplePermissions(rel, ATT_TABLE);
+			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 			/* Performs own recursion */
 			if (rel->rd_rel->relhasoids)
 			{
@@ -3280,17 +3281,23 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			pass = AT_PASS_MISC;
 			break;
 		case AT_AddInherit:		/* INHERIT */
-			ATSimplePermissions(rel, ATT_TABLE);
+			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 			/* This command never recurses */
 			ATPrepAddInherit(rel);
 			pass = AT_PASS_MISC;
 			break;
+		case AT_DropInherit:	/* NO INHERIT */
+			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+			/* This command never recurses */
+			/* No command-specific prep needed */
+			pass = AT_PASS_MISC;
+			break;
 		case AT_AlterConstraint:		/* ALTER CONSTRAINT */
 			ATSimplePermissions(rel, ATT_TABLE);
 			pass = AT_PASS_MISC;
 			break;
 		case AT_ValidateConstraint:		/* VALIDATE CONSTRAINT */
-			ATSimplePermissions(rel, ATT_TABLE);
+			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 			/* Recursion occurs during execution phase */
 			/* No command-specific prep needed except saving recurse flag */
 			if (recurse)
@@ -3318,7 +3325,6 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_EnableAlwaysRule:
 		case AT_EnableReplicaRule:
 		case AT_DisableRule:
-		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
 		case AT_EnableRowSecurity:
@@ -4637,7 +4643,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
-		ATSimplePermissions(rel, ATT_TABLE);
+		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
@@ -5533,7 +5539,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
-		ATSimplePermissions(rel, ATT_TABLE);
+		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
 	/*
 	 * get the number of the attribute
@@ -5926,7 +5932,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
-		ATSimplePermissions(rel, ATT_TABLE);
+		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
 	/*
 	 * Call AddRelationNewConstraints to do the work, making sure it works on
@@ -7084,6 +7090,10 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	bool		isnull;
 	Snapshot	snapshot;
 
+	/* VALIDATE CONSTRAINT is a no-op for foreign tables */
+	if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+		return;
+
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrtup);
 
 	estate = CreateExecutorState();
@@ -7426,7 +7436,7 @@ ATExecDropConstraint(Relation rel, const char *constrName,
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
-		ATSimplePermissions(rel, ATT_TABLE);
+		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
 	conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -9681,7 +9691,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
 	 * Must be owner of both parent and child -- child was checked by
 	 * ATSimplePermissions call in ATPrepCmd
 	 */
-	ATSimplePermissions(parent_rel, ATT_TABLE);
+	ATSimplePermissions(parent_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
 
 	/* Permanent rels cannot inherit from temporary ones */
 	if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c
index 1c8be25da7d88bc5fdb71aa78a3ec9c6eef5213a..d87be963a95131e68678e500cdf60e07066e9ef3 100644
--- a/src/backend/executor/execCurrent.c
+++ b/src/backend/executor/execCurrent.c
@@ -106,7 +106,7 @@ execCurrentOf(CurrentOfExpr *cexpr,
 			if (!RowMarkRequiresRowShareLock(thiserm->markType))
 				continue;		/* ignore non-FOR UPDATE/SHARE items */
 
-			if (RelationGetRelid(thiserm->relation) == table_oid)
+			if (thiserm->relid == table_oid)
 			{
 				if (erm)
 					ereport(ERROR,
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 13ceffae5c46390e1bb912dea8c7294ac7e17ccc..ad7e2072908a54e7cd0761027c803c7bbad62512 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -814,21 +814,22 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 		if (rc->isParent)
 			continue;
 
+		/* get relation's OID (will produce InvalidOid if subquery) */
+		relid = getrelid(rc->rti, rangeTable);
+
 		switch (rc->markType)
 		{
 			case ROW_MARK_EXCLUSIVE:
 			case ROW_MARK_NOKEYEXCLUSIVE:
 			case ROW_MARK_SHARE:
 			case ROW_MARK_KEYSHARE:
-				relid = getrelid(rc->rti, rangeTable);
 				relation = heap_open(relid, RowShareLock);
 				break;
 			case ROW_MARK_REFERENCE:
-				relid = getrelid(rc->rti, rangeTable);
 				relation = heap_open(relid, AccessShareLock);
 				break;
 			case ROW_MARK_COPY:
-				/* there's no real table here ... */
+				/* no physical table access is required */
 				relation = NULL;
 				break;
 			default:
@@ -843,6 +844,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
 		erm = (ExecRowMark *) palloc(sizeof(ExecRowMark));
 		erm->relation = relation;
+		erm->relid = relid;
 		erm->rti = rc->rti;
 		erm->prti = rc->prti;
 		erm->rowmarkId = rc->rowmarkId;
@@ -1911,21 +1913,9 @@ ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
 	aerm->rowmark = erm;
 
 	/* Look up the resjunk columns associated with this rowmark */
-	if (erm->relation)
+	if (erm->markType != ROW_MARK_COPY)
 	{
-		Assert(erm->markType != ROW_MARK_COPY);
-
-		/* if child rel, need tableoid */
-		if (erm->rti != erm->prti)
-		{
-			snprintf(resname, sizeof(resname), "tableoid%u", erm->rowmarkId);
-			aerm->toidAttNo = ExecFindJunkAttributeInTlist(targetlist,
-														   resname);
-			if (!AttributeNumberIsValid(aerm->toidAttNo))
-				elog(ERROR, "could not find junk %s column", resname);
-		}
-
-		/* always need ctid for real relations */
+		/* need ctid for all methods other than COPY */
 		snprintf(resname, sizeof(resname), "ctid%u", erm->rowmarkId);
 		aerm->ctidAttNo = ExecFindJunkAttributeInTlist(targetlist,
 													   resname);
@@ -1934,8 +1924,7 @@ ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
 	}
 	else
 	{
-		Assert(erm->markType == ROW_MARK_COPY);
-
+		/* need wholerow if COPY */
 		snprintf(resname, sizeof(resname), "wholerow%u", erm->rowmarkId);
 		aerm->wholeAttNo = ExecFindJunkAttributeInTlist(targetlist,
 														resname);
@@ -1943,6 +1932,16 @@ ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
 			elog(ERROR, "could not find junk %s column", resname);
 	}
 
+	/* if child rel, need tableoid */
+	if (erm->rti != erm->prti)
+	{
+		snprintf(resname, sizeof(resname), "tableoid%u", erm->rowmarkId);
+		aerm->toidAttNo = ExecFindJunkAttributeInTlist(targetlist,
+													   resname);
+		if (!AttributeNumberIsValid(aerm->toidAttNo))
+			elog(ERROR, "could not find junk %s column", resname);
+	}
+
 	return aerm;
 }
 
@@ -2375,31 +2374,32 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 		/* clear any leftover test tuple for this rel */
 		EvalPlanQualSetTuple(epqstate, erm->rti, NULL);
 
-		if (erm->relation)
+		/* if child rel, must check whether it produced this row */
+		if (erm->rti != erm->prti)
 		{
-			Buffer		buffer;
+			Oid			tableoid;
 
-			Assert(erm->markType == ROW_MARK_REFERENCE);
+			datum = ExecGetJunkAttribute(epqstate->origslot,
+										 aerm->toidAttNo,
+										 &isNull);
+			/* non-locked rels could be on the inside of outer joins */
+			if (isNull)
+				continue;
+			tableoid = DatumGetObjectId(datum);
 
-			/* if child rel, must check whether it produced this row */
-			if (erm->rti != erm->prti)
+			Assert(OidIsValid(erm->relid));
+			if (tableoid != erm->relid)
 			{
-				Oid			tableoid;
+				/* this child is inactive right now */
+				continue;
+			}
+		}
 
-				datum = ExecGetJunkAttribute(epqstate->origslot,
-											 aerm->toidAttNo,
-											 &isNull);
-				/* non-locked rels could be on the inside of outer joins */
-				if (isNull)
-					continue;
-				tableoid = DatumGetObjectId(datum);
+		if (erm->markType == ROW_MARK_REFERENCE)
+		{
+			Buffer		buffer;
 
-				if (tableoid != RelationGetRelid(erm->relation))
-				{
-					/* this child is inactive right now */
-					continue;
-				}
-			}
+			Assert(erm->relation != NULL);
 
 			/* fetch the tuple's ctid */
 			datum = ExecGetJunkAttribute(epqstate->origslot,
@@ -2439,8 +2439,7 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 			tuple.t_len = HeapTupleHeaderGetDatumLength(td);
 			ItemPointerSetInvalid(&(tuple.t_self));
 			/* relation might be a foreign table, if so provide tableoid */
-			tuple.t_tableOid = getrelid(erm->rti,
-										epqstate->estate->es_range_table);
+			tuple.t_tableOid = erm->relid;
 			tuple.t_data = td;
 
 			/* copy and store tuple */
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 48107d93567ad1802f14c3ca557410f441acf599..bb6df47a95d15ff33910c1b8057af3adf4e53b00 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -93,7 +93,8 @@ lnext:
 				elog(ERROR, "tableoid is NULL");
 			tableoid = DatumGetObjectId(datum);
 
-			if (tableoid != RelationGetRelid(erm->relation))
+			Assert(OidIsValid(erm->relid));
+			if (tableoid != erm->relid)
 			{
 				/* this child is inactive right now */
 				ItemPointerSetInvalid(&(erm->curCtid));
@@ -174,8 +175,9 @@ lnext:
 				}
 
 				/* updated, so fetch and lock the updated version */
-				copyTuple = EvalPlanQualFetch(estate, erm->relation, lockmode,
-											  erm->waitPolicy, &hufd.ctid, hufd.xmax);
+				copyTuple = EvalPlanQualFetch(estate, erm->relation,
+											  lockmode, erm->waitPolicy,
+											  &hufd.ctid, hufd.xmax);
 
 				if (copyTuple == NULL)
 				{
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 05687a48c9a4a721e2885e499ce59a5bc643dcf8..876a87ff52a6cf757d46679eae31c20069a66496 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2224,35 +2224,7 @@ preprocess_rowmarks(PlannerInfo *root)
 		newrc = makeNode(PlanRowMark);
 		newrc->rti = newrc->prti = rc->rti;
 		newrc->rowmarkId = ++(root->glob->lastRowMarkId);
-		if (rte->relkind == RELKIND_FOREIGN_TABLE)
-		{
-			/* For now, we force all foreign tables to use ROW_MARK_COPY */
-			newrc->markType = ROW_MARK_COPY;
-		}
-		else
-		{
-			/* regular table, apply the appropriate lock type */
-			switch (rc->strength)
-			{
-				case LCS_NONE:
-					/* we intentionally throw an error for LCS_NONE */
-					elog(ERROR, "unrecognized LockClauseStrength %d",
-						 (int) rc->strength);
-					break;
-				case LCS_FORKEYSHARE:
-					newrc->markType = ROW_MARK_KEYSHARE;
-					break;
-				case LCS_FORSHARE:
-					newrc->markType = ROW_MARK_SHARE;
-					break;
-				case LCS_FORNOKEYUPDATE:
-					newrc->markType = ROW_MARK_NOKEYEXCLUSIVE;
-					break;
-				case LCS_FORUPDATE:
-					newrc->markType = ROW_MARK_EXCLUSIVE;
-					break;
-			}
-		}
+		newrc->markType = select_rowmark_type(rte, rc->strength);
 		newrc->allMarkTypes = (1 << newrc->markType);
 		newrc->strength = rc->strength;
 		newrc->waitPolicy = rc->waitPolicy;
@@ -2277,12 +2249,7 @@ preprocess_rowmarks(PlannerInfo *root)
 		newrc = makeNode(PlanRowMark);
 		newrc->rti = newrc->prti = i;
 		newrc->rowmarkId = ++(root->glob->lastRowMarkId);
-		/* real tables support REFERENCE, anything else needs COPY */
-		if (rte->rtekind == RTE_RELATION &&
-			rte->relkind != RELKIND_FOREIGN_TABLE)
-			newrc->markType = ROW_MARK_REFERENCE;
-		else
-			newrc->markType = ROW_MARK_COPY;
+		newrc->markType = select_rowmark_type(rte, LCS_NONE);
 		newrc->allMarkTypes = (1 << newrc->markType);
 		newrc->strength = LCS_NONE;
 		newrc->waitPolicy = LockWaitBlock;		/* doesn't matter */
@@ -2294,6 +2261,49 @@ preprocess_rowmarks(PlannerInfo *root)
 	root->rowMarks = prowmarks;
 }
 
+/*
+ * Select RowMarkType to use for a given table
+ */
+RowMarkType
+select_rowmark_type(RangeTblEntry *rte, LockClauseStrength strength)
+{
+	if (rte->rtekind != RTE_RELATION)
+	{
+		/* If it's not a table at all, use ROW_MARK_COPY */
+		return ROW_MARK_COPY;
+	}
+	else if (rte->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		/* For now, we force all foreign tables to use ROW_MARK_COPY */
+		return ROW_MARK_COPY;
+	}
+	else
+	{
+		/* Regular table, apply the appropriate lock type */
+		switch (strength)
+		{
+			case LCS_NONE:
+				/* don't need tuple lock, only ability to re-fetch the row */
+				return ROW_MARK_REFERENCE;
+				break;
+			case LCS_FORKEYSHARE:
+				return ROW_MARK_KEYSHARE;
+				break;
+			case LCS_FORSHARE:
+				return ROW_MARK_SHARE;
+				break;
+			case LCS_FORNOKEYUPDATE:
+				return ROW_MARK_NOKEYEXCLUSIVE;
+				break;
+			case LCS_FORUPDATE:
+				return ROW_MARK_EXCLUSIVE;
+				break;
+		}
+		elog(ERROR, "unrecognized LockClauseStrength %d", (int) strength);
+		return ROW_MARK_EXCLUSIVE;		/* keep compiler quiet */
+	}
+}
+
 /*
  * preprocess_limit - do pre-estimation for LIMIT and/or OFFSET clauses
  *
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index cd40afce767cc45fdb3397bec65c2d71a7659ce6..51b3da21b30c9696bbe73bd0d26732c3d9ce9102 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1337,12 +1337,13 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 
 		/*
 		 * Build an RTE for the child, and attach to query's rangetable list.
-		 * We copy most fields of the parent's RTE, but replace relation OID,
-		 * and set inh = false.  Also, set requiredPerms to zero since all
-		 * required permissions checks are done on the original RTE.
+		 * We copy most fields of the parent's RTE, but replace relation OID
+		 * and relkind, and set inh = false.  Also, set requiredPerms to zero
+		 * since all required permissions checks are done on the original RTE.
 		 */
 		childrte = copyObject(rte);
 		childrte->relid = childOID;
+		childrte->relkind = newrelation->rd_rel->relkind;
 		childrte->inh = false;
 		childrte->requiredPerms = 0;
 		parse->rtable = lappend(parse->rtable, childrte);
@@ -1388,7 +1389,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 			newrc->rti = childRTindex;
 			newrc->prti = rti;
 			newrc->rowmarkId = oldrc->rowmarkId;
-			newrc->markType = oldrc->markType;
+			/* Reselect rowmark type, because relkind might not match parent */
+			newrc->markType = select_rowmark_type(childrte, oldrc->strength);
 			newrc->allMarkTypes = (1 << newrc->markType);
 			newrc->strength = oldrc->strength;
 			newrc->waitPolicy = oldrc->waitPolicy;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 82405b9d26f5b8c9325132eb9ba59a72cad29bd0..873ca79492dc7a61d9769ce0cc608570c59b367c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2762,6 +2762,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->relation = $4;
 					n->tableElts = $6;
 					n->inhRelations = $8;
+					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->options = $9;
 					n->oncommit = $10;
@@ -2778,6 +2779,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->relation = $7;
 					n->tableElts = $9;
 					n->inhRelations = $11;
+					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->options = $12;
 					n->oncommit = $13;
@@ -2792,6 +2794,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->inhRelations = NIL;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
@@ -2808,6 +2811,7 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->inhRelations = NIL;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
@@ -4360,32 +4364,42 @@ AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
 CreateForeignTableStmt:
 		CREATE FOREIGN TABLE qualified_name
 			'(' OptTableElementList ')'
-			SERVER name create_generic_options
+			OptInherit SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
 					$4->relpersistence = RELPERSISTENCE_PERMANENT;
 					n->base.relation = $4;
 					n->base.tableElts = $6;
-					n->base.inhRelations = NIL;
+					n->base.inhRelations = $8;
+					n->base.ofTypename = NULL;
+					n->base.constraints = NIL;
+					n->base.options = NIL;
+					n->base.oncommit = ONCOMMIT_NOOP;
+					n->base.tablespacename = NULL;
 					n->base.if_not_exists = false;
 					/* FDW-specific data */
-					n->servername = $9;
-					n->options = $10;
+					n->servername = $10;
+					n->options = $11;
 					$$ = (Node *) n;
 				}
 		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
 			'(' OptTableElementList ')'
-			SERVER name create_generic_options
+			OptInherit SERVER name create_generic_options
 				{
 					CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
 					$7->relpersistence = RELPERSISTENCE_PERMANENT;
 					n->base.relation = $7;
 					n->base.tableElts = $9;
-					n->base.inhRelations = NIL;
+					n->base.inhRelations = $11;
+					n->base.ofTypename = NULL;
+					n->base.constraints = NIL;
+					n->base.options = NIL;
+					n->base.oncommit = ONCOMMIT_NOOP;
+					n->base.tablespacename = NULL;
 					n->base.if_not_exists = true;
 					/* FDW-specific data */
-					n->servername = $12;
-					n->options = $13;
+					n->servername = $13;
+					n->options = $14;
 					$$ = (Node *) n;
 				}
 		;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f24fefa2753bf7d671c25f53f40ebf0a74a55b12..7da5c411949a4b665f63ba5d6c645b94e7aeac93 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -13748,8 +13748,9 @@ dumpTableSchema(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo)
 		 * attislocal correctly, plus fix up any inherited CHECK constraints.
 		 * Analogously, we set up typed tables using ALTER TABLE / OF here.
 		 */
-		if (dopt->binary_upgrade && (tbinfo->relkind == RELKIND_RELATION ||
-								   tbinfo->relkind == RELKIND_FOREIGN_TABLE))
+		if (dopt->binary_upgrade &&
+			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
 			{
@@ -13771,15 +13772,13 @@ dumpTableSchema(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
-						appendPQExpBuffer(q, "ALTER FOREIGN TABLE %s ",
+						appendPQExpBuffer(q, "ALTER FOREIGN TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
-
 					appendPQExpBuffer(q, "DROP COLUMN %s;\n",
 									  fmtId(tbinfo->attnames[j]));
 				}
 				else if (!tbinfo->attislocal[j])
 				{
-					Assert(tbinfo->relkind != RELKIND_FOREIGN_TABLE);
 					appendPQExpBufferStr(q, "\n-- For binary upgrade, recreate inherited column.\n");
 					appendPQExpBufferStr(q, "UPDATE pg_catalog.pg_attribute\n"
 										 "SET attislocal = false\n"
@@ -13985,7 +13984,8 @@ dumpTableSchema(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo)
 	/*
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
-	if ((tbinfo->relkind == RELKIND_RELATION || tbinfo->relkind == RELKIND_MATVIEW) &&
+	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
 		if (tbinfo->relreplident == REPLICA_IDENTITY_INDEX)
@@ -14004,6 +14004,10 @@ dumpTableSchema(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo)
 		}
 	}
 
+	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE && tbinfo->hasoids)
+		appendPQExpBuffer(q, "\nALTER TABLE ONLY %s SET WITH OIDS;\n",
+						  fmtId(tbinfo->dobj.name));
+
 	if (dopt->binary_upgrade)
 		binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 59b17f3c993ee4a43c2b045f954d25a383e74723..ac75f86fef7e5a25f7ece603d4bd88275120d3b9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -414,17 +414,20 @@ typedef struct EState
  * ExecRowMark -
  *	   runtime representation of FOR [KEY] UPDATE/SHARE clauses
  *
- * When doing UPDATE, DELETE, or SELECT FOR [KEY] UPDATE/SHARE, we should have an
+ * When doing UPDATE, DELETE, or SELECT FOR [KEY] UPDATE/SHARE, we will have an
  * ExecRowMark for each non-target relation in the query (except inheritance
- * parent RTEs, which can be ignored at runtime).  See PlanRowMark for details
- * about most of the fields.  In addition to fields directly derived from
- * PlanRowMark, we store curCtid, which is used by the WHERE CURRENT OF code.
+ * parent RTEs, which can be ignored at runtime).  Virtual relations such as
+ * subqueries-in-FROM will have an ExecRowMark with relation == NULL.  See
+ * PlanRowMark for details about most of the fields.  In addition to fields
+ * directly derived from PlanRowMark, we store curCtid, which is used by the
+ * WHERE CURRENT OF code.
  *
  * EState->es_rowMarks is a list of these structs.
  */
 typedef struct ExecRowMark
 {
 	Relation	relation;		/* opened and suitably locked relation */
+	Oid			relid;			/* its OID (or InvalidOid, if subquery) */
 	Index		rti;			/* its range table index */
 	Index		prti;			/* parent range table index, if child */
 	Index		rowmarkId;		/* unique identifier for resjunk columns */
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index cd62aec08f1061cfb0447f2627964964ac80e2b7..b10a5040f5b8eb21a71f945bb97f8e500b517d82 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -40,6 +40,9 @@ extern void add_tlist_costs_to_plan(PlannerInfo *root, Plan *plan,
 
 extern bool is_dummy_plan(Plan *plan);
 
+extern RowMarkType select_rowmark_type(RangeTblEntry *rte,
+					LockClauseStrength strength);
+
 extern Expr *expression_planner(Expr *expr);
 
 extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index 512d97ff6dcbf4b02b0dff19de4099e0378f9c3f..73c02bb10f0143df5c10fd4e9fbeac9914bbf308 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -755,6 +755,7 @@ ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 OPTIONS (SET p2 'V2', DROP p1);
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET STATISTICS 10000;
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET (n_distinct = 100);
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STORAGE PLAIN;
 \d+ ft1
                                       Foreign table "public.ft1"
  Column |  Type   | Modifiers |          FDW Options           | Storage  | Stats target | Description 
@@ -766,7 +767,7 @@ ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
  c5     | integer |           |                                | plain    |              | 
  c6     | integer | not null  |                                | plain    |              | 
  c7     | integer |           | (p1 'v1', p2 'v2')             | plain    |              | 
- c8     | text    |           | (p2 'V2')                      | extended |              | 
+ c8     | text    |           | (p2 'V2')                      | plain    |              | 
  c9     | integer |           |                                | plain    |              | 
  c10    | integer |           | (p1 'v1')                      | plain    |              | 
 Check constraints:
@@ -784,9 +785,7 @@ ALTER FOREIGN TABLE ft1 ADD PRIMARY KEY (c7);                   -- ERROR
 ERROR:  primary key constraints are not supported on foreign tables
 LINE 1: ALTER FOREIGN TABLE ft1 ADD PRIMARY KEY (c7);
                                     ^
-ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID;  -- ERROR
-ERROR:  CHECK constraints on foreign tables cannot be marked NOT VALID
-ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0);
+ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID;
 ALTER FOREIGN TABLE ft1 ALTER CONSTRAINT ft1_c9_check DEFERRABLE; -- ERROR
 ERROR:  "ft1" is not a table
 ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check;
@@ -794,8 +793,7 @@ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const;               -- ERROR
 ERROR:  constraint "no_const" of relation "ft1" does not exist
 ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
 NOTICE:  constraint "no_const" of relation "ft1" does not exist, skipping
-ALTER FOREIGN TABLE ft1 SET WITH OIDS;                          -- ERROR
-ERROR:  "ft1" is not a table
+ALTER FOREIGN TABLE ft1 SET WITH OIDS;
 ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
 ALTER FOREIGN TABLE ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@');
 ALTER FOREIGN TABLE ft1 DROP COLUMN no_column;                  -- ERROR
@@ -1234,6 +1232,501 @@ DROP TRIGGER trigtest_before_row ON foreign_schema.foreign_table_1;
 DROP TRIGGER trigtest_after_stmt ON foreign_schema.foreign_table_1;
 DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
 DROP FUNCTION dummy_trigger();
+-- Table inheritance
+CREATE TABLE pt1 (
+	c1 integer NOT NULL,
+	c2 text,
+	c3 date
+);
+CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+  SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+\d+ pt1
+                          Table "public.pt1"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ c1     | integer | not null  | plain    |              | 
+ c2     | text    |           | extended |              | 
+ c3     | date    |           | plain    |              | 
+Child tables: ft2
+
+\d+ ft2
+                             Foreign table "public.ft2"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    |              | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+Server: s0
+FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: pt1
+
+DROP FOREIGN TABLE ft2;
+\d+ pt1
+                          Table "public.pt1"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ c1     | integer | not null  | plain    |              | 
+ c2     | text    |           | extended |              | 
+ c3     | date    |           | plain    |              | 
+
+CREATE FOREIGN TABLE ft2 (
+	c1 integer NOT NULL,
+	c2 text,
+	c3 date
+) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+\d+ ft2
+                             Foreign table "public.ft2"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    |              | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+Server: s0
+FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+
+ALTER FOREIGN TABLE ft2 INHERIT pt1;
+\d+ pt1
+                          Table "public.pt1"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ c1     | integer | not null  | plain    |              | 
+ c2     | text    |           | extended |              | 
+ c3     | date    |           | plain    |              | 
+Child tables: ft2
+
+\d+ ft2
+                             Foreign table "public.ft2"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    |              | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+Server: s0
+FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: pt1
+
+CREATE TABLE ct3() INHERITS(ft2);
+CREATE FOREIGN TABLE ft3 (
+	c1 integer NOT NULL,
+	c2 text,
+	c3 date
+) INHERITS(ft2)
+  SERVER s0;
+NOTICE:  merging column "c1" with inherited definition
+NOTICE:  merging column "c2" with inherited definition
+NOTICE:  merging column "c3" with inherited definition
+\d+ ft2
+                             Foreign table "public.ft2"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    |              | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+Server: s0
+FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: pt1
+Child tables: ct3,
+              ft3
+
+\d+ ct3
+                          Table "public.ct3"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ c1     | integer | not null  | plain    |              | 
+ c2     | text    |           | extended |              | 
+ c3     | date    |           | plain    |              | 
+Inherits: ft2
+
+\d+ ft3
+                             Foreign table "public.ft3"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    |              | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+Server: s0
+Inherits: ft2
+
+-- add attributes recursively
+ALTER TABLE pt1 ADD COLUMN c4 integer;
+ALTER TABLE pt1 ADD COLUMN c5 integer DEFAULT 0;
+ALTER TABLE pt1 ADD COLUMN c6 integer;
+ALTER TABLE pt1 ADD COLUMN c7 integer NOT NULL;
+ALTER TABLE pt1 ADD COLUMN c8 integer;
+\d+ pt1
+                          Table "public.pt1"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ c1     | integer | not null  | plain    |              | 
+ c2     | text    |           | extended |              | 
+ c3     | date    |           | plain    |              | 
+ c4     | integer |           | plain    |              | 
+ c5     | integer | default 0 | plain    |              | 
+ c6     | integer |           | plain    |              | 
+ c7     | integer | not null  | plain    |              | 
+ c8     | integer |           | plain    |              | 
+Child tables: ft2
+
+\d+ ft2
+                             Foreign table "public.ft2"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    |              | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+ c4     | integer |           |             | plain    |              | 
+ c5     | integer | default 0 |             | plain    |              | 
+ c6     | integer |           |             | plain    |              | 
+ c7     | integer | not null  |             | plain    |              | 
+ c8     | integer |           |             | plain    |              | 
+Server: s0
+FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: pt1
+Child tables: ct3,
+              ft3
+
+\d+ ct3
+                          Table "public.ct3"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ c1     | integer | not null  | plain    |              | 
+ c2     | text    |           | extended |              | 
+ c3     | date    |           | plain    |              | 
+ c4     | integer |           | plain    |              | 
+ c5     | integer | default 0 | plain    |              | 
+ c6     | integer |           | plain    |              | 
+ c7     | integer | not null  | plain    |              | 
+ c8     | integer |           | plain    |              | 
+Inherits: ft2
+
+\d+ ft3
+                             Foreign table "public.ft3"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    |              | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+ c4     | integer |           |             | plain    |              | 
+ c5     | integer | default 0 |             | plain    |              | 
+ c6     | integer |           |             | plain    |              | 
+ c7     | integer | not null  |             | plain    |              | 
+ c8     | integer |           |             | plain    |              | 
+Server: s0
+Inherits: ft2
+
+-- alter attributes recursively
+ALTER TABLE pt1 ALTER COLUMN c4 SET DEFAULT 0;
+ALTER TABLE pt1 ALTER COLUMN c5 DROP DEFAULT;
+ALTER TABLE pt1 ALTER COLUMN c6 SET NOT NULL;
+ALTER TABLE pt1 ALTER COLUMN c7 DROP NOT NULL;
+ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10) USING '0';        -- ERROR
+ERROR:  "ft2" is not a table
+ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10);
+ALTER TABLE pt1 ALTER COLUMN c8 SET DATA TYPE text;
+ALTER TABLE pt1 ALTER COLUMN c1 SET STATISTICS 10000;
+ALTER TABLE pt1 ALTER COLUMN c1 SET (n_distinct = 100);
+ALTER TABLE pt1 ALTER COLUMN c8 SET STATISTICS -1;
+ALTER TABLE pt1 ALTER COLUMN c8 SET STORAGE EXTERNAL;
+\d+ pt1
+                          Table "public.pt1"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ c1     | integer | not null  | plain    | 10000        | 
+ c2     | text    |           | extended |              | 
+ c3     | date    |           | plain    |              | 
+ c4     | integer | default 0 | plain    |              | 
+ c5     | integer |           | plain    |              | 
+ c6     | integer | not null  | plain    |              | 
+ c7     | integer |           | plain    |              | 
+ c8     | text    |           | external |              | 
+Child tables: ft2
+
+\d+ ft2
+                             Foreign table "public.ft2"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    | 10000        | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+ c4     | integer | default 0 |             | plain    |              | 
+ c5     | integer |           |             | plain    |              | 
+ c6     | integer | not null  |             | plain    |              | 
+ c7     | integer |           |             | plain    |              | 
+ c8     | text    |           |             | external |              | 
+Server: s0
+FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: pt1
+Child tables: ct3,
+              ft3
+
+-- drop attributes recursively
+ALTER TABLE pt1 DROP COLUMN c4;
+ALTER TABLE pt1 DROP COLUMN c5;
+ALTER TABLE pt1 DROP COLUMN c6;
+ALTER TABLE pt1 DROP COLUMN c7;
+ALTER TABLE pt1 DROP COLUMN c8;
+\d+ pt1
+                          Table "public.pt1"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ c1     | integer | not null  | plain    | 10000        | 
+ c2     | text    |           | extended |              | 
+ c3     | date    |           | plain    |              | 
+Child tables: ft2
+
+\d+ ft2
+                             Foreign table "public.ft2"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    | 10000        | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+Server: s0
+FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: pt1
+Child tables: ct3,
+              ft3
+
+-- add constraints recursively
+ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (c1 > 0) NO INHERIT;
+ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+-- connoinherit should be true for NO INHERIT constraint
+SELECT relname, conname, contype, conislocal, coninhcount, connoinherit
+  FROM pg_class AS pc JOIN pg_constraint AS pgc ON (conrelid = pc.oid)
+  WHERE pc.relname = 'pt1'
+  ORDER BY 1,2;
+ relname | conname | contype | conislocal | coninhcount | connoinherit 
+---------+---------+---------+------------+-------------+--------------
+ pt1     | pt1chk1 | c       | t          |           0 | t
+ pt1     | pt1chk2 | c       | t          |           0 | f
+(2 rows)
+
+-- child does not inherit NO INHERIT constraints
+\d+ pt1
+                          Table "public.pt1"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ c1     | integer | not null  | plain    | 10000        | 
+ c2     | text    |           | extended |              | 
+ c3     | date    |           | plain    |              | 
+Check constraints:
+    "pt1chk1" CHECK (c1 > 0) NO INHERIT
+    "pt1chk2" CHECK (c2 <> ''::text)
+Child tables: ft2
+
+\d+ ft2
+                             Foreign table "public.ft2"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    | 10000        | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+Check constraints:
+    "pt1chk2" CHECK (c2 <> ''::text)
+Server: s0
+FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: pt1
+Child tables: ct3,
+              ft3
+
+DROP FOREIGN TABLE ft2; -- ERROR
+ERROR:  cannot drop foreign table ft2 because other objects depend on it
+DETAIL:  table ct3 depends on foreign table ft2
+foreign table ft3 depends on foreign table ft2
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP FOREIGN TABLE ft2 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to table ct3
+drop cascades to foreign table ft3
+CREATE FOREIGN TABLE ft2 (
+	c1 integer NOT NULL,
+	c2 text,
+	c3 date
+) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+-- child must have parent's INHERIT constraints
+ALTER FOREIGN TABLE ft2 INHERIT pt1;                            -- ERROR
+ERROR:  child table is missing constraint "pt1chk2"
+ALTER FOREIGN TABLE ft2 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ALTER FOREIGN TABLE ft2 INHERIT pt1;
+-- child does not inherit NO INHERIT constraints
+\d+ pt1
+                          Table "public.pt1"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ c1     | integer | not null  | plain    | 10000        | 
+ c2     | text    |           | extended |              | 
+ c3     | date    |           | plain    |              | 
+Check constraints:
+    "pt1chk1" CHECK (c1 > 0) NO INHERIT
+    "pt1chk2" CHECK (c2 <> ''::text)
+Child tables: ft2
+
+\d+ ft2
+                             Foreign table "public.ft2"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    |              | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+Check constraints:
+    "pt1chk2" CHECK (c2 <> ''::text)
+Server: s0
+FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: pt1
+
+-- drop constraints recursively
+ALTER TABLE pt1 DROP CONSTRAINT pt1chk1 CASCADE;
+ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+-- NOT VALID case
+INSERT INTO pt1 VALUES (1, 'pt1'::text, '1994-01-01'::date);
+ALTER TABLE pt1 ADD CONSTRAINT pt1chk3 CHECK (c2 <> '') NOT VALID;
+\d+ pt1
+                          Table "public.pt1"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ c1     | integer | not null  | plain    | 10000        | 
+ c2     | text    |           | extended |              | 
+ c3     | date    |           | plain    |              | 
+Check constraints:
+    "pt1chk3" CHECK (c2 <> ''::text) NOT VALID
+Child tables: ft2
+
+\d+ ft2
+                             Foreign table "public.ft2"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    |              | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+Check constraints:
+    "pt1chk2" CHECK (c2 <> ''::text)
+    "pt1chk3" CHECK (c2 <> ''::text) NOT VALID
+Server: s0
+FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: pt1
+
+-- VALIDATE CONSTRAINT need do nothing on foreign tables
+ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk3;
+\d+ pt1
+                          Table "public.pt1"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ c1     | integer | not null  | plain    | 10000        | 
+ c2     | text    |           | extended |              | 
+ c3     | date    |           | plain    |              | 
+Check constraints:
+    "pt1chk3" CHECK (c2 <> ''::text)
+Child tables: ft2
+
+\d+ ft2
+                             Foreign table "public.ft2"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    |              | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+Check constraints:
+    "pt1chk2" CHECK (c2 <> ''::text)
+    "pt1chk3" CHECK (c2 <> ''::text)
+Server: s0
+FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: pt1
+
+-- OID system column
+ALTER TABLE pt1 SET WITH OIDS;
+\d+ pt1
+                          Table "public.pt1"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ c1     | integer | not null  | plain    | 10000        | 
+ c2     | text    |           | extended |              | 
+ c3     | date    |           | plain    |              | 
+Check constraints:
+    "pt1chk3" CHECK (c2 <> ''::text)
+Child tables: ft2
+Has OIDs: yes
+
+\d+ ft2
+                             Foreign table "public.ft2"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    |              | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+Check constraints:
+    "pt1chk2" CHECK (c2 <> ''::text)
+    "pt1chk3" CHECK (c2 <> ''::text)
+Server: s0
+FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: pt1
+Has OIDs: yes
+
+ALTER TABLE ft2 SET WITHOUT OIDS;  -- ERROR
+ERROR:  cannot drop inherited column "oid"
+ALTER TABLE pt1 SET WITHOUT OIDS;
+\d+ pt1
+                          Table "public.pt1"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ c1     | integer | not null  | plain    | 10000        | 
+ c2     | text    |           | extended |              | 
+ c3     | date    |           | plain    |              | 
+Check constraints:
+    "pt1chk3" CHECK (c2 <> ''::text)
+Child tables: ft2
+
+\d+ ft2
+                             Foreign table "public.ft2"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ c1     | integer | not null  |             | plain    |              | 
+ c2     | text    |           |             | extended |              | 
+ c3     | date    |           |             | plain    |              | 
+Check constraints:
+    "pt1chk2" CHECK (c2 <> ''::text)
+    "pt1chk3" CHECK (c2 <> ''::text)
+Server: s0
+FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: pt1
+
+-- changes name of an attribute recursively
+ALTER TABLE pt1 RENAME COLUMN c1 TO f1;
+ALTER TABLE pt1 RENAME COLUMN c2 TO f2;
+ALTER TABLE pt1 RENAME COLUMN c3 TO f3;
+-- changes name of a constraint recursively
+ALTER TABLE pt1 RENAME CONSTRAINT pt1chk3 TO f2_check;
+\d+ pt1
+                          Table "public.pt1"
+ Column |  Type   | Modifiers | Storage  | Stats target | Description 
+--------+---------+-----------+----------+--------------+-------------
+ f1     | integer | not null  | plain    | 10000        | 
+ f2     | text    |           | extended |              | 
+ f3     | date    |           | plain    |              | 
+Check constraints:
+    "f2_check" CHECK (f2 <> ''::text)
+Child tables: ft2
+
+\d+ ft2
+                             Foreign table "public.ft2"
+ Column |  Type   | Modifiers | FDW Options | Storage  | Stats target | Description 
+--------+---------+-----------+-------------+----------+--------------+-------------
+ f1     | integer | not null  |             | plain    |              | 
+ f2     | text    |           |             | extended |              | 
+ f3     | date    |           |             | plain    |              | 
+Check constraints:
+    "f2_check" CHECK (f2 <> ''::text)
+    "pt1chk2" CHECK (f2 <> ''::text)
+Server: s0
+FDW Options: (delimiter ',', quote '"', "be quoted" 'value')
+Inherits: pt1
+
+-- TRUNCATE doesn't work on foreign tables, either directly or recursively
+TRUNCATE ft2;  -- ERROR
+ERROR:  "ft2" is not a table
+TRUNCATE pt1;  -- ERROR
+ERROR:  "ft2" is not a table
+DROP TABLE pt1 CASCADE;
+NOTICE:  drop cascades to foreign table ft2
 -- IMPORT FOREIGN SCHEMA
 IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
 ERROR:  foreign-data wrapper "foo" has no handler
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index f41bef170443fe04a4ac2462a5b167564c9fdd84..44e8dab44c27f9ff130be8d5f7d89898b79012b4 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -1037,6 +1037,9 @@ EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);
                 QUERY PLAN                 
 -------------------------------------------
  Update on t1 t1_3
+   Update on t1 t1_3
+   Update on t2 t1
+   Update on t3 t1
    ->  Subquery Scan on t1
          Filter: f_leak(t1.b)
          ->  LockRows
@@ -1052,7 +1055,7 @@ EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);
          ->  LockRows
                ->  Seq Scan on t3
                      Filter: ((a % 2) = 0)
-(16 rows)
+(19 rows)
 
 UPDATE t1 SET b = b || b WHERE f_leak(b);
 NOTICE:  f_leak => bbb
@@ -1149,6 +1152,9 @@ EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b);
                 QUERY PLAN                 
 -------------------------------------------
  Delete on t1 t1_3
+   Delete on t1 t1_3
+   Delete on t2 t1
+   Delete on t3 t1
    ->  Subquery Scan on t1
          Filter: f_leak(t1.b)
          ->  LockRows
@@ -1164,7 +1170,7 @@ EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b);
          ->  LockRows
                ->  Seq Scan on t3
                      Filter: ((a % 2) = 0)
-(16 rows)
+(19 rows)
 
 DELETE FROM only t1 WHERE f_leak(b) RETURNING oid, *, t1;
 NOTICE:  f_leak => bbbbbb_updt
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index c49e769bf830b78cf72c9bbea0d749a486a68f8d..9e7ba7247103edc997617cb1fd169476d4730d31 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2064,6 +2064,10 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a = 3;
                                                              QUERY PLAN                                                             
 ------------------------------------------------------------------------------------------------------------------------------------
  Update on public.t1 t1_4
+   Update on public.t1 t1_4
+   Update on public.t11 t1
+   Update on public.t12 t1
+   Update on public.t111 t1
    ->  Subquery Scan on t1
          Output: 100, t1.b, t1.c, t1.ctid
          Filter: snoop(t1.a)
@@ -2132,7 +2136,7 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a = 3;
                            ->  Seq Scan on public.t111 t111_4
                                  Output: t111_4.ctid, t111_4.tableoid, t111_4.a
                                  Filter: (t111_4.a = 3)
-(69 rows)
+(73 rows)
 
 UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a = 3;
 SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100
@@ -2150,6 +2154,10 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
                                                              QUERY PLAN                                                             
 ------------------------------------------------------------------------------------------------------------------------------------
  Update on public.t1 t1_4
+   Update on public.t1 t1_4
+   Update on public.t11 t1
+   Update on public.t12 t1
+   Update on public.t111 t1
    ->  Subquery Scan on t1
          Output: (t1.a + 1), t1.b, t1.c, t1.ctid
          Filter: snoop(t1.a)
@@ -2218,7 +2226,7 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
                            ->  Seq Scan on public.t111 t111_4
                                  Output: t111_4.ctid, t111_4.tableoid, t111_4.a
                                  Filter: (t111_4.a = 8)
-(69 rows)
+(73 rows)
 
 UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
 NOTICE:  snooped value: 8
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 524e0ef2c68d4cc5d2d1406bb1fc5ffd8c900a4f..6986f4777efa8389068fb72f2bbd9d86c0685bb4 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -2086,6 +2086,10 @@ DELETE FROM a USING wcte WHERE aa = q2;
                    QUERY PLAN                   
 ------------------------------------------------
  Delete on public.a
+   Delete on public.a
+   Delete on public.b
+   Delete on public.c
+   Delete on public.d
    CTE wcte
      ->  Insert on public.int8_tbl
            Output: int8_tbl.q2
@@ -2119,7 +2123,7 @@ DELETE FROM a USING wcte WHERE aa = q2;
                Output: d.ctid, d.aa
          ->  CTE Scan on wcte
                Output: wcte.*, wcte.q2
-(34 rows)
+(38 rows)
 
 -- error cases
 -- data-modifying WITH tries to use its own output
diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql
index b4b999d444e200c5567d445035cf38ac31eaa004..af3d531289a0d23092653704ef4dc8eda5995ae7 100644
--- a/src/test/regress/sql/foreign_data.sql
+++ b/src/test/regress/sql/foreign_data.sql
@@ -328,19 +328,19 @@ ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 OPTIONS (SET p2 'V2', DROP p1);
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET STATISTICS 10000;
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 SET (n_distinct = 100);
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STORAGE PLAIN;
 \d+ ft1
 -- can't change the column type if it's used elsewhere
 CREATE TABLE use_ft1_column_type (x ft1);
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer;	-- ERROR
 DROP TABLE use_ft1_column_type;
 ALTER FOREIGN TABLE ft1 ADD PRIMARY KEY (c7);                   -- ERROR
-ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID;  -- ERROR
-ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0);
+ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0) NOT VALID;
 ALTER FOREIGN TABLE ft1 ALTER CONSTRAINT ft1_c9_check DEFERRABLE; -- ERROR
 ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check;
 ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const;               -- ERROR
 ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
-ALTER FOREIGN TABLE ft1 SET WITH OIDS;                          -- ERROR
+ALTER FOREIGN TABLE ft1 SET WITH OIDS;
 ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
 ALTER FOREIGN TABLE ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@');
 ALTER FOREIGN TABLE ft1 DROP COLUMN no_column;                  -- ERROR
@@ -536,6 +536,137 @@ DROP TRIGGER trigtest_after_row ON foreign_schema.foreign_table_1;
 
 DROP FUNCTION dummy_trigger();
 
+-- Table inheritance
+CREATE TABLE pt1 (
+	c1 integer NOT NULL,
+	c2 text,
+	c3 date
+);
+CREATE FOREIGN TABLE ft2 () INHERITS (pt1)
+  SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+\d+ pt1
+\d+ ft2
+DROP FOREIGN TABLE ft2;
+\d+ pt1
+CREATE FOREIGN TABLE ft2 (
+	c1 integer NOT NULL,
+	c2 text,
+	c3 date
+) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+\d+ ft2
+ALTER FOREIGN TABLE ft2 INHERIT pt1;
+\d+ pt1
+\d+ ft2
+CREATE TABLE ct3() INHERITS(ft2);
+CREATE FOREIGN TABLE ft3 (
+	c1 integer NOT NULL,
+	c2 text,
+	c3 date
+) INHERITS(ft2)
+  SERVER s0;
+\d+ ft2
+\d+ ct3
+\d+ ft3
+
+-- add attributes recursively
+ALTER TABLE pt1 ADD COLUMN c4 integer;
+ALTER TABLE pt1 ADD COLUMN c5 integer DEFAULT 0;
+ALTER TABLE pt1 ADD COLUMN c6 integer;
+ALTER TABLE pt1 ADD COLUMN c7 integer NOT NULL;
+ALTER TABLE pt1 ADD COLUMN c8 integer;
+\d+ pt1
+\d+ ft2
+\d+ ct3
+\d+ ft3
+
+-- alter attributes recursively
+ALTER TABLE pt1 ALTER COLUMN c4 SET DEFAULT 0;
+ALTER TABLE pt1 ALTER COLUMN c5 DROP DEFAULT;
+ALTER TABLE pt1 ALTER COLUMN c6 SET NOT NULL;
+ALTER TABLE pt1 ALTER COLUMN c7 DROP NOT NULL;
+ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10) USING '0';        -- ERROR
+ALTER TABLE pt1 ALTER COLUMN c8 TYPE char(10);
+ALTER TABLE pt1 ALTER COLUMN c8 SET DATA TYPE text;
+ALTER TABLE pt1 ALTER COLUMN c1 SET STATISTICS 10000;
+ALTER TABLE pt1 ALTER COLUMN c1 SET (n_distinct = 100);
+ALTER TABLE pt1 ALTER COLUMN c8 SET STATISTICS -1;
+ALTER TABLE pt1 ALTER COLUMN c8 SET STORAGE EXTERNAL;
+\d+ pt1
+\d+ ft2
+
+-- drop attributes recursively
+ALTER TABLE pt1 DROP COLUMN c4;
+ALTER TABLE pt1 DROP COLUMN c5;
+ALTER TABLE pt1 DROP COLUMN c6;
+ALTER TABLE pt1 DROP COLUMN c7;
+ALTER TABLE pt1 DROP COLUMN c8;
+\d+ pt1
+\d+ ft2
+
+-- add constraints recursively
+ALTER TABLE pt1 ADD CONSTRAINT pt1chk1 CHECK (c1 > 0) NO INHERIT;
+ALTER TABLE pt1 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+-- connoinherit should be true for NO INHERIT constraint
+SELECT relname, conname, contype, conislocal, coninhcount, connoinherit
+  FROM pg_class AS pc JOIN pg_constraint AS pgc ON (conrelid = pc.oid)
+  WHERE pc.relname = 'pt1'
+  ORDER BY 1,2;
+-- child does not inherit NO INHERIT constraints
+\d+ pt1
+\d+ ft2
+DROP FOREIGN TABLE ft2; -- ERROR
+DROP FOREIGN TABLE ft2 CASCADE;
+CREATE FOREIGN TABLE ft2 (
+	c1 integer NOT NULL,
+	c2 text,
+	c3 date
+) SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
+-- child must have parent's INHERIT constraints
+ALTER FOREIGN TABLE ft2 INHERIT pt1;                            -- ERROR
+ALTER FOREIGN TABLE ft2 ADD CONSTRAINT pt1chk2 CHECK (c2 <> '');
+ALTER FOREIGN TABLE ft2 INHERIT pt1;
+-- child does not inherit NO INHERIT constraints
+\d+ pt1
+\d+ ft2
+
+-- drop constraints recursively
+ALTER TABLE pt1 DROP CONSTRAINT pt1chk1 CASCADE;
+ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
+
+-- NOT VALID case
+INSERT INTO pt1 VALUES (1, 'pt1'::text, '1994-01-01'::date);
+ALTER TABLE pt1 ADD CONSTRAINT pt1chk3 CHECK (c2 <> '') NOT VALID;
+\d+ pt1
+\d+ ft2
+-- VALIDATE CONSTRAINT need do nothing on foreign tables
+ALTER TABLE pt1 VALIDATE CONSTRAINT pt1chk3;
+\d+ pt1
+\d+ ft2
+
+-- OID system column
+ALTER TABLE pt1 SET WITH OIDS;
+\d+ pt1
+\d+ ft2
+ALTER TABLE ft2 SET WITHOUT OIDS;  -- ERROR
+ALTER TABLE pt1 SET WITHOUT OIDS;
+\d+ pt1
+\d+ ft2
+
+-- changes name of an attribute recursively
+ALTER TABLE pt1 RENAME COLUMN c1 TO f1;
+ALTER TABLE pt1 RENAME COLUMN c2 TO f2;
+ALTER TABLE pt1 RENAME COLUMN c3 TO f3;
+-- changes name of a constraint recursively
+ALTER TABLE pt1 RENAME CONSTRAINT pt1chk3 TO f2_check;
+\d+ pt1
+\d+ ft2
+
+-- TRUNCATE doesn't work on foreign tables, either directly or recursively
+TRUNCATE ft2;  -- ERROR
+TRUNCATE pt1;  -- ERROR
+
+DROP TABLE pt1 CASCADE;
+
 -- IMPORT FOREIGN SCHEMA
 IMPORT FOREIGN SCHEMA s1 FROM SERVER s9 INTO public; -- ERROR
 IMPORT FOREIGN SCHEMA s1 LIMIT TO (t1) FROM SERVER s9 INTO public; --ERROR