pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
To: Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>
Cc: shigeru.hanada@gmail.com
Cc: pgsql-hackers@postgresql.org
Subject: Re: inherit support for foreign tables
Date: Thu, 27 Mar 2014 21:53:16 +0900
Message-ID: <53341F3C.2050609@lab.ntt.co.jp> (raw)
In-Reply-To: <533383BF.1050004@lab.ntt.co.jp>
References: <20140318.183856.121817800.horiguchi.kyotaro@lab.ntt.co.jp>
	<532AE62A.6020307@lab.ntt.co.jp>
	<532F8EC1.6020401@lab.ntt.co.jp>
	<20140326.171454.142890940.horiguchi.kyotaro@lab.ntt.co.jp>
	<533383BF.1050004@lab.ntt.co.jp>
List-Unsubscribe: <mailto:majordomo@postgresql.org?body=unsub%20pgsql-hackers>

(2014/03/27 10:49), Etsuro Fujita wrote:
> (2014/03/26 17:14), Kyotaro HORIGUCHI wrote:
>> Finally, although I insist the necessity of the warning for child
>> foreign tables on alter table, if you belive it to be put off,
>> I'll compromise by putting a note to CF-app that last judgement
>> is left to committer.
> 
> OK  So, if there are no objections of other, I'll mark this patch as
> "ready for committer" and do that.

I'll do it right now, since there seems to be no objections of others.

I've revised the patch a bit further, mainly the documentation.  Patch
attached.

Note: as mentioned above, Horiguchi-san insisted that warning
functionality for recursive ALTER TABLE operations for inheritance trees
that contain one or more children that are foreign.  However, that
functionality hasn't been included in the patch, since ISTM that that
functionality should be implemented as a separate patch [1].  We leave
this for commiters to decide.

[1] http://www.postgresql.org/message-id/5321ABD2.6000104@lab.ntt.co.jp

Thanks,

Best regards,
Etsuro Fujita
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
***************
*** 117,122 **** static void fileGetForeignRelSize(PlannerInfo *root,
--- 117,127 ----
  static void fileGetForeignPaths(PlannerInfo *root,
  					RelOptInfo *baserel,
  					Oid foreigntableid);
+ static ForeignPath *fileReparameterizeForeignPath(PlannerInfo *root,
+ 												  RelOptInfo *baserel,
+ 												  Oid foreigntableid,
+ 												  ForeignPath *path,
+ 												  Relids required_outer);
  static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
  				   RelOptInfo *baserel,
  				   Oid foreigntableid,
***************
*** 145,151 **** static bool check_selective_binary_conversion(RelOptInfo *baserel,
  static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
  			  FileFdwPlanState *fdw_private);
  static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
! 			   FileFdwPlanState *fdw_private,
  			   Cost *startup_cost, Cost *total_cost);
  static int file_acquire_sample_rows(Relation onerel, int elevel,
  						 HeapTuple *rows, int targrows,
--- 150,156 ----
  static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
  			  FileFdwPlanState *fdw_private);
  static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
! 			   FileFdwPlanState *fdw_private, List *join_conds,
  			   Cost *startup_cost, Cost *total_cost);
  static int file_acquire_sample_rows(Relation onerel, int elevel,
  						 HeapTuple *rows, int targrows,
***************
*** 163,168 **** file_fdw_handler(PG_FUNCTION_ARGS)
--- 168,174 ----
  
  	fdwroutine->GetForeignRelSize = fileGetForeignRelSize;
  	fdwroutine->GetForeignPaths = fileGetForeignPaths;
+ 	fdwroutine->ReparameterizeForeignPath = fileReparameterizeForeignPath;
  	fdwroutine->GetForeignPlan = fileGetForeignPlan;
  	fdwroutine->ExplainForeignScan = fileExplainForeignScan;
  	fdwroutine->BeginForeignScan = fileBeginForeignScan;
***************
*** 517,523 **** fileGetForeignPaths(PlannerInfo *root,
  										  (Node *) columns));
  
  	/* Estimate costs */
! 	estimate_costs(root, baserel, fdw_private,
  				   &startup_cost, &total_cost);
  
  	/*
--- 523,530 ----
  										  (Node *) columns));
  
  	/* Estimate costs */
! 	estimate_costs(root, baserel,
! 				   fdw_private, NIL,
  				   &startup_cost, &total_cost);
  
  	/*
***************
*** 542,547 **** fileGetForeignPaths(PlannerInfo *root,
--- 549,589 ----
  }
  
  /*
+  * fileReparameterizeForeignPath
+  *		Attempt to modify a given path to have greater parameterization
+  */
+ static ForeignPath *
+ fileReparameterizeForeignPath(PlannerInfo *root,
+ 							  RelOptInfo *baserel,
+ 							  Oid foreigntableid,
+ 							  ForeignPath *path,
+ 							  Relids required_outer)
+ {
+ 	ParamPathInfo *param_info;
+ 	FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private;
+ 	Cost		startup_cost;
+ 	Cost		total_cost;
+ 
+ 	/* Get the ParamPathInfo */
+ 	param_info = get_baserel_parampathinfo(root, baserel, required_outer);
+ 
+ 	/* Redo the cost estimates */
+ 	estimate_costs(root, baserel,
+ 				   fdw_private,
+ 				   param_info->ppi_clauses,
+ 				   &startup_cost, &total_cost);
+ 
+ 	/* Make and return the new path */
+ 	return create_foreignscan_path(root, baserel,
+ 								   param_info->ppi_rows,
+ 								   startup_cost,
+ 								   total_cost,
+ 								   NIL,		/* no pathkeys */
+ 								   required_outer,
+ 								   path->fdw_private);
+ }
+ 
+ /*
   * fileGetForeignPlan
   *		Create a ForeignScan plan node for scanning the foreign table
   */
***************
*** 970,981 **** estimate_size(PlannerInfo *root, RelOptInfo *baserel,
   */
  static void
  estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
! 			   FileFdwPlanState *fdw_private,
  			   Cost *startup_cost, Cost *total_cost)
  {
  	BlockNumber pages = fdw_private->pages;
  	double		ntuples = fdw_private->ntuples;
  	Cost		run_cost = 0;
  	Cost		cpu_per_tuple;
  
  	/*
--- 1012,1024 ----
   */
  static void
  estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
! 			   FileFdwPlanState *fdw_private, List *join_conds,
  			   Cost *startup_cost, Cost *total_cost)
  {
  	BlockNumber pages = fdw_private->pages;
  	double		ntuples = fdw_private->ntuples;
  	Cost		run_cost = 0;
+ 	QualCost	join_cost;
  	Cost		cpu_per_tuple;
  
  	/*
***************
*** 986,993 **** estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
  	 */
  	run_cost += seq_page_cost * pages;
  
! 	*startup_cost = baserel->baserestrictcost.startup;
! 	cpu_per_tuple = cpu_tuple_cost * 10 + baserel->baserestrictcost.per_tuple;
  	run_cost += cpu_per_tuple * ntuples;
  	*total_cost = *startup_cost + run_cost;
  }
--- 1029,1039 ----
  	 */
  	run_cost += seq_page_cost * pages;
  
! 	cost_qual_eval(&join_cost, join_conds, root);
! 	*startup_cost =
! 		(baserel->baserestrictcost.startup + join_cost.startup);
! 	cpu_per_tuple = cpu_tuple_cost * 10 +
! 		(baserel->baserestrictcost.per_tuple + join_cost.per_tuple);
  	run_cost += cpu_per_tuple * ntuples;
  	*total_cost = *startup_cost + run_cost;
  }
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 241,246 **** static void postgresGetForeignRelSize(PlannerInfo *root,
--- 241,251 ----
  static void postgresGetForeignPaths(PlannerInfo *root,
  						RelOptInfo *baserel,
  						Oid foreigntableid);
+ static ForeignPath *postgresReparameterizeForeignPath(PlannerInfo *root,
+ 													  RelOptInfo *baserel,
+ 													  Oid foreigntableid,
+ 													  ForeignPath *path,
+ 													  Relids required_outer);
  static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
  					   RelOptInfo *baserel,
  					   Oid foreigntableid,
***************
*** 342,347 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 347,353 ----
  	/* Functions for scanning foreign tables */
  	routine->GetForeignRelSize = postgresGetForeignRelSize;
  	routine->GetForeignPaths = postgresGetForeignPaths;
+ 	routine->ReparameterizeForeignPath = postgresReparameterizeForeignPath;
  	routine->GetForeignPlan = postgresGetForeignPlan;
  	routine->BeginForeignScan = postgresBeginForeignScan;
  	routine->IterateForeignScan = postgresIterateForeignScan;
***************
*** 729,734 **** postgresGetForeignPaths(PlannerInfo *root,
--- 735,782 ----
  }
  
  /*
+  * postgresReparameterizeForeignPath
+  *		Attempt to modify a given path to have greater parameterization
+  */
+ static ForeignPath *
+ postgresReparameterizeForeignPath(PlannerInfo *root,
+ 								  RelOptInfo *baserel,
+ 								  Oid foreigntableid,
+ 								  ForeignPath *path,
+ 								  Relids required_outer)
+ {
+ 	ParamPathInfo *param_info;
+ 	double		rows;
+ 	int			width;
+ 	Cost		startup_cost;
+ 	Cost		total_cost;
+ 
+ 	/* Get the ParamPathInfo */
+ 	param_info = get_baserel_parampathinfo(root, baserel, required_outer);
+ 
+ 	/* Redo the cost estimates */
+ 	estimate_path_cost_size(root, baserel,
+ 							param_info->ppi_clauses,
+ 							&rows, &width,
+ 							&startup_cost, &total_cost);
+ 
+ 	/*
+ 	 * ppi_rows currently won't get looked at by anything, but still we
+ 	 * may as well ensure that it matches our idea of the rowcount.
+ 	 */
+ 	param_info->ppi_rows = rows;
+ 
+ 	/* Make and return the new path */
+ 	return create_foreignscan_path(root, baserel,
+ 								   rows,
+ 								   startup_cost,
+ 								   total_cost,
+ 								   NIL,		/* no pathkeys */
+ 								   required_outer,
+ 								   NIL);	/* no fdw_private list */
+ }
+ 
+ /*
   * postgresGetForeignPlan
   *		Create ForeignScan plan node which implements selected best path
   */
***************
*** 1775,1785 **** estimate_path_cost_size(PlannerInfo *root,
  	}
  	else
  	{
! 		/*
! 		 * We don't support join conditions in this mode (hence, no
! 		 * parameterized paths can be made).
! 		 */
! 		Assert(join_conds == NIL);
  
  		/* Use rows/width estimates made by set_baserel_size_estimates. */
  		rows = baserel->rows;
--- 1823,1830 ----
  	}
  	else
  	{
! 		Selectivity join_sel;
! 		QualCost	join_cost;
  
  		/* Use rows/width estimates made by set_baserel_size_estimates. */
  		rows = baserel->rows;
***************
*** 1792,1808 **** estimate_path_cost_size(PlannerInfo *root,
  		retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel);
  		retrieved_rows = Min(retrieved_rows, baserel->tuples);
  
  		/*
  		 * Cost as though this were a seqscan, which is pessimistic.  We
! 		 * effectively imagine the local_conds are being evaluated remotely,
! 		 * too.
  		 */
  		startup_cost = 0;
  		run_cost = 0;
  		run_cost += seq_page_cost * baserel->pages;
  
! 		startup_cost += baserel->baserestrictcost.startup;
! 		cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
  		run_cost += cpu_per_tuple * baserel->tuples;
  
  		total_cost = startup_cost + run_cost;
--- 1837,1865 ----
  		retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel);
  		retrieved_rows = Min(retrieved_rows, baserel->tuples);
  
+ 		/* Factor in the selectivity of the join_conds */
+ 		join_sel = clauselist_selectivity(root,
+ 										  join_conds,
+ 										  baserel->relid,
+ 										  JOIN_INNER,
+ 										  NULL);
+ 
+ 		rows = clamp_row_est(rows * join_sel);
+ 
  		/*
  		 * Cost as though this were a seqscan, which is pessimistic.  We
! 		 * effectively imagine the local_conds and join_conds are being
! 		 * evaluated remotely, too.
  		 */
  		startup_cost = 0;
  		run_cost = 0;
  		run_cost += seq_page_cost * baserel->pages;
  
! 		cost_qual_eval(&join_cost, join_conds, root);
! 		startup_cost +=
! 			(baserel->baserestrictcost.startup + join_cost.startup);
! 		cpu_per_tuple = cpu_tuple_cost +
! 			(baserel->baserestrictcost.per_tuple + join_cost.per_tuple);
  		run_cost += cpu_per_tuple * baserel->tuples;
  
  		total_cost = startup_cost + run_cost;
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 162,167 **** GetForeignPaths (PlannerInfo *root,
--- 162,198 ----
  
      <para>
  <programlisting>
+ ForeignPath *
+ ReparameterizeForeignPath (PlannerInfo *root,
+                            RelOptInfo *baserel,
+                            Oid foreigntableid,
+                            ForeignPath *path,
+                            Relids required_outer);
+ </programlisting>
+ 
+      Create an access path for a scan on a foreign table using join
+      clauses, which is called a <quote>parameterized path</>.
+      This is called during query planning.
+      The parameters are as for <function>GetForeignRelSize</>, plus
+      the <structname>ForeignPath</> (previously produced by
+      <function>GetForeignPaths</>), and the IDs of all other tables
+      that provide the join clauses.
+     </para>
+ 
+     <para>
+      This function must generate a parameterized path for a scan on the
+      foreign table.  The parameterized path must contain cost estimates,
+      and can contain any FDW-private information that is needed to identify
+      the specific scan method intended.  Unlike the other scan-related
+      functions, this function is optional.
+     </para>
+ 
+     <para>
+      See <xref linkend="fdw-planning"> for additional information.
+     </para>
+ 
+     <para>
+ <programlisting>
  ForeignScan *
  GetForeignPlan (PlannerInfo *root,
                  RelOptInfo *baserel,
***************
*** 804,813 **** GetForeignServerByName(const char *name, bool missing_ok);
  
      <para>
       The FDW callback functions <function>GetForeignRelSize</>,
!      <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
!      <function>PlanForeignModify</> must fit into the workings of the
!      <productname>PostgreSQL</> planner.  Here are some notes about what
!      they must do.
      </para>
  
      <para>
--- 835,844 ----
  
      <para>
       The FDW callback functions <function>GetForeignRelSize</>,
!      <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
!      <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
!      into the workings of the <productname>PostgreSQL</> planner.  Here are
!      some notes about what they must do.
      </para>
  
      <para>
***************
*** 837,850 **** GetForeignServerByName(const char *name, bool missing_ok);
       to initialize it to NULL when the <literal>baserel</> node is created.
       It is useful for passing information forward from
       <function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
!      <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
!      avoiding recalculation.
      </para>
  
      <para>
!      <function>GetForeignPaths</> can identify the meaning of different
!      access paths by storing private information in the
!      <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
       <structfield>fdw_private</> is declared as a <type>List</> pointer, but
       could actually contain anything since the core planner does not touch
       it.  However, best practice is to use a representation that's dumpable
--- 868,884 ----
       to initialize it to NULL when the <literal>baserel</> node is created.
       It is useful for passing information forward from
       <function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
!      <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
!      and/or
!      <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
!      to <function>GetForeignPlan</>, thereby avoiding recalculation.
      </para>
  
      <para>
!      <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
!      can identify the meaning of different access paths by storing private
!      information in the <structfield>fdw_private</> field of
!      <structname>ForeignPath</> nodes.
       <structfield>fdw_private</> is declared as a <type>List</> pointer, but
       could actually contain anything since the core planner does not touch
       it.  However, best practice is to use a representation that's dumpable
***************
*** 887,896 **** GetForeignServerByName(const char *name, bool missing_ok);
       <replaceable>sub_expression</>, which it determines can be executed on
       the remote server given the locally-evaluated value of the
       <replaceable>sub_expression</>.  The actual identification of such a
!      clause should happen during <function>GetForeignPaths</>, since it would
!      affect the cost estimate for the path.  The path's
!      <structfield>fdw_private</> field would probably include a pointer to
!      the identified clause's <structname>RestrictInfo</> node.  Then
       <function>GetForeignPlan</> would remove that clause from scan_clauses,
       but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
       to ensure that it gets massaged into executable form.  It would probably
--- 921,931 ----
       <replaceable>sub_expression</>, which it determines can be executed on
       the remote server given the locally-evaluated value of the
       <replaceable>sub_expression</>.  The actual identification of such a
!      clause should happen during <function>GetForeignPaths</> or
!      <function>ReparameterizeForeignPath</>, since it would affect the cost
!      estimate for the path.  The path's <structfield>fdw_private</> field
!      would probably include a pointer to the identified clause's
!      <structname>RestrictInfo</> node.  Then
       <function>GetForeignPlan</> would remove that clause from scan_clauses,
       but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
       to ensure that it gets massaged into executable form.  It would probably
*** a/doc/src/sgml/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 42,47 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,51 ----
      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> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+     ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+     DROP CONSTRAINT [ IF EXISTS ]  <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
+     INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+     NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
      OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
      OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
  </synopsis>
***************
*** 149,154 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 153,202 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+     <listitem>
+      <para>
+       This form adds a new constraint to a table using the same syntax as
+       <xref linkend="SQL-CREATEFOREIGNTABLE">.
+       Unlike the case when adding a constraint to a regular table, nothing happens
+       to the underlying storage: this action simply declares that
+       some new constraint holds for all rows in the foreign table.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+     <listitem>
+      <para>
+       This form drops the specified constraint on a table.
+       If <literal>IF EXISTS</literal> is specified and the constraint
+       does not exist, no error is thrown. In this case a notice is issued instead.
+      </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.  The parent table must be an ordinary table.
+      </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>
       <para>
***************
*** 270,275 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 318,341 ----
       </varlistentry>
  
       <varlistentry>
+       <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+       <listitem>
+        <para>
+         New table constraint for the table.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+       <listitem>
+        <para>
+         Name of an existing constraint to drop.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
        <term><literal>CASCADE</literal></term>
        <listitem>
         <para>
***************
*** 290,295 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 356,372 ----
       </varlistentry>
  
       <varlistentry>
+       <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+       <listitem>
+        <para>
+         A parent table to associate or de-associate with this foreign table.
+         The parent table must be an ordinary table.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+ 
+      <varlistentry>
        <term><replaceable class="PARAMETER">new_owner</replaceable></term>
        <listitem>
         <para>
***************
*** 306,312 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
         </para>
        </listitem>
       </varlistentry>
-     </variablelist>
   </refsect1>
  
   <refsect1>
--- 383,388 ----
***************
*** 319,328 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
     <para>
      Consistency with the foreign server is not checked when a column is added
      or removed with <literal>ADD COLUMN</literal> or
!     <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
!     added, or a column type is changed with <literal>SET DATA TYPE</>.  It is
!     the user's responsibility to ensure that the table definition matches the
!     remote side.
     </para>
  
     <para>
--- 395,404 ----
     <para>
      Consistency with the foreign server is not checked when a column is added
      or removed with <literal>ADD COLUMN</literal> or
!     <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
!     <literal>CHECK</> constraint is added, or a column type is changed with
!     <literal>SET DATA TYPE</>.  It is the user's responsibility to ensure that
!     the table definition matches the remote side.
     </para>
  
     <para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 937,942 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 937,949 ----
     </para>
  
     <para>
+     If a table has any descendant tables that are foreign, a recursive
+     <literal>SET STORAGE</literal> operation will be rejected since it
+     is not permitted to add an <literal>oid</literal> system column to
+     foreign tables.
+    </para>
+ 
+    <para>
      The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
      and <literal>TABLESPACE</> actions never recurse to descendant tables;
      that is, they always act as though <literal>ONLY</> were specified.
***************
*** 945,950 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 952,970 ----
     </para>
  
     <para>
+     When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+     </literal> option recursively, an inherited constraint on a descendant
+     table that is foreign will be marked valid without checking
+     consistency with the foreign server.
+    </para>
+ 
+    <para>
+     A recursive <literal>SET STORAGE</literal> operation will make the
+     storage mode for a descendant table's column unchanged if the table is
+     foreign.
+    </para>
+ 
+    <para>
      Changing any part of a system catalog table is not permitted.
     </para>
  
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
    </para>
  
    <para>
+     The inheritance statistics for a parent table that contains one or more
+     children that are foreign tables are collected only when explicitly
+     selected.  If the foreign table's wrapper does not support
+     <command>ANALYZE</command>, the command prints a warning and does nothing.
+   </para>
+ 
+   <para>
      If the table being analyzed is completely empty, <command>ANALYZE</command>
      will not record new statistics for that table.  Any existing statistics
      will be retained.
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 19,27 ****
   <refsynopsisdiv>
  <synopsis>
  CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
!     <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
      [, ... ]
  ] )
    SERVER <replaceable class="parameter">server_name</replaceable>
  [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
  
--- 19,29 ----
   <refsynopsisdiv>
  <synopsis>
  CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
!   { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
!     | <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>' [, ... ] ) ]
  
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 32,44 ----
  [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
  { NOT NULL |
    NULL |
+   CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] |
    DEFAULT <replaceable>default_expr</replaceable> }
+ 
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+ 
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] }
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 146,177 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] </literal></term>
+     <listitem>
+      <para>
+       The <literal>CHECK</> clause specifies an expression producing a
+       Boolean result which each row must satisfy.
+       Expressions evaluating to TRUE or UNKNOWN succeed.
+       A check constraint specified as a column constraint should
+       reference that column's value only, while an expression
+       appearing in a table constraint can reference multiple columns.
+      </para>
+ 
+      <para>
+       Currently, <literal>CHECK</literal> expressions cannot contain
+       subqueries nor refer to variables other than columns of the
+       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>
+ 
+    <varlistentry>
      <term><literal>DEFAULT
      <replaceable>default_expr</replaceable></literal></term>
      <listitem>
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 193,210 ----
     </varlistentry>
  
     <varlistentry>
+     <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+     <listitem>
+      <para>
+       The name of an existing table from which the new foreign table
+       automatically inherits all columns.  The specified parent table
+       must be an ordinary table.  See <xref linkend="ddl-inherit"> for the
+       details of table inheritance.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><replaceable class="PARAMETER">server_name</replaceable></term>
      <listitem>
       <para>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 233,249 ----
  
   </refsect1>
  
+  <refsect1>
+   <title>Notes</title>
+ 
+    <para>
+     Constraints on foreign tables are not enforced on insert or update.
+     Those definitions simply declare the constraints hold for all rows
+     in the foreign tables.  It is the user's responsibility to ensure
+     that those definitions match the remote side.  Such constraints are
+     used for some kind of query optimization such as constraint exclusion.
+    </para>
+  </refsect1>
  
   <refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
    <title>Examples</title>
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 82,87 **** int			default_statistics_target = 100;
--- 82,88 ----
  
  /* A few variables that don't seem worth passing around as parameters */
  static MemoryContext anl_context = NULL;
+ static VacuumMode vac_mode;
  static BufferAccessStrategy vac_strategy;
  
  
***************
*** 115,121 **** static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
   *	analyze_rel() -- analyze one relation
   */
  void
! analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
  {
  	Relation	onerel;
  	int			elevel;
--- 116,125 ----
   *	analyze_rel() -- analyze one relation
   */
  void
! analyze_rel(Oid relid,
! 			VacuumStmt *vacstmt,
! 			VacuumMode vacmode,
! 			BufferAccessStrategy bstrategy)
  {
  	Relation	onerel;
  	int			elevel;
***************
*** 129,134 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
--- 133,139 ----
  		elevel = DEBUG2;
  
  	/* Set up static variables */
+ 	vac_mode = vacmode;
  	vac_strategy = bstrategy;
  
  	/*
***************
*** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1457,1463 ----
  {
  	List	   *tableOIDs;
  	Relation   *rels;
+ 	AcquireSampleRowsFunc *acquirefunc;
  	double	   *relblocks;
  	double		totalblocks;
  	int			numrows,
***************
*** 1486,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1492,1499 ----
  	 * BlockNumber, so we use double arithmetic.
  	 */
  	rels = (Relation *) palloc(list_length(tableOIDs) * sizeof(Relation));
+ 	acquirefunc = (AcquireSampleRowsFunc *) palloc(list_length(tableOIDs)
+ 											* sizeof(AcquireSampleRowsFunc));
  	relblocks = (double *) palloc(list_length(tableOIDs) * sizeof(double));
  	totalblocks = 0;
  	nrels = 0;
***************
*** 1507,1513 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
  		}
  
  		rels[nrels] = childrel;
! 		relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
  		totalblocks += relblocks[nrels];
  		nrels++;
  	}
--- 1515,1554 ----
  		}
  
  		rels[nrels] = childrel;
! 
! 		if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
! 		{
! 			FdwRoutine *fdwroutine;
! 			BlockNumber relpages = 0;
! 			bool		ok = false;
! 
! 			/* Ignore unless analyzing a specific inheritance tree */
! 			if (vac_mode != VAC_MODE_SINGLE)
! 				return 0;
! 
! 			/* Check whether the FDW supports analysis */
! 			fdwroutine = GetFdwRoutineForRelation(childrel, false);
! 			if (fdwroutine->AnalyzeForeignTable != NULL)
! 				ok = fdwroutine->AnalyzeForeignTable(childrel,
! 													 &acquirefunc[nrels],
! 													 &relpages);
! 			if (!ok)
! 			{
! 				/* Give up if the FDW doesn't support analysis */
! 				ereport(WARNING,
! 						(errmsg("skipping analyze of inheritance tree \"%s\" --- cannot analyze foreign table \"%s\"",
! 								RelationGetRelationName(onerel),
! 								RelationGetRelationName(childrel))));
! 				return 0;
! 			}
! 			relblocks[nrels] = (double) relpages;
! 		}
! 		else
! 		{
! 			acquirefunc[nrels] = acquire_sample_rows;
! 			relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
! 		}
! 
  		totalblocks += relblocks[nrels];
  		nrels++;
  	}
***************
*** 1525,1530 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1566,1572 ----
  	{
  		Relation	childrel = rels[i];
  		double		childblocks = relblocks[i];
+ 		AcquireSampleRowsFunc childacquirefunc = acquirefunc[i];
  
  		if (childblocks > 0)
  		{
***************
*** 1540,1551 **** 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);
  
  				/* We may need to convert from child's rowtype to parent's */
  				if (childrows > 0 &&
--- 1582,1593 ----
  							tdrows;
  
  				/* Fetch a random sample of the child's rows */
! 				childrows = childacquirefunc(childrel,
! 											 elevel,
! 											 rows + numrows,
! 											 childtargrows,
! 											 &trows,
! 											 &tdrows);
  
  				/* We may need to convert from child's rowtype to parent's */
  				if (childrows > 0 &&
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 311,317 **** static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
  static void ATSimplePermissions(Relation rel, int allowed_targets);
  static void ATWrongRelkindError(Relation rel, int allowed_targets);
  static void ATSimpleRecursion(List **wqueue, Relation rel,
! 				  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode);
  static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
  					  LOCKMODE lockmode);
  static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
--- 311,318 ----
  static void ATSimplePermissions(Relation rel, int allowed_targets);
  static void ATWrongRelkindError(Relation rel, int allowed_targets);
  static void ATSimpleRecursion(List **wqueue, Relation rel,
! 				  AlterTableCmd *cmd, bool recurse,
! 				  bool include_foreign, LOCKMODE lockmode);
  static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
  					  LOCKMODE lockmode);
  static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
***************
*** 467,476 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
  				 errmsg("ON COMMIT can only be used on temporary tables")));
- 	if (stmt->constraints != NIL && relkind == RELKIND_FOREIGN_TABLE)
- 		ereport(ERROR,
- 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- 				 errmsg("constraints are not supported on foreign tables")));
  
  	/*
  	 * Look up the namespace in which we are supposed to create the relation,
--- 468,473 ----
***************
*** 3019,3042 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			 * rules.
  			 */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  			/* No command-specific prep needed */
  			pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
  			break;
  		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_DROP;
  			break;
  		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_ADD_CONSTR;
  			break;
  		case AT_SetStatistics:	/* ALTER COLUMN SET STATISTICS */
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  			/* Performs own permission checks */
  			ATPrepSetStatistics(rel, cmd->name, cmd->def, lockmode);
  			pass = AT_PASS_MISC;
--- 3016,3043 ----
  			 * rules.
  			 */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
! 			/* Recurse to child tables that are foreign, too */
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode);
  			/* No command-specific prep needed */
  			pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
  			break;
  		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
! 			/* Recurse to child tables that are foreign, too */
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_DROP;
  			break;
  		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
! 			/* Recurse to child tables that are foreign, too */
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_ADD_CONSTR;
  			break;
  		case AT_SetStatistics:	/* ALTER COLUMN SET STATISTICS */
! 			/* Recurse to child tables that are foreign, too */
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode);
  			/* Performs own permission checks */
  			ATPrepSetStatistics(rel, cmd->name, cmd->def, lockmode);
  			pass = AT_PASS_MISC;
***************
*** 3049,3055 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			break;
  		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_MISC;
  			break;
--- 3050,3057 ----
  			break;
  		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
! 			/* Don't recurse to child tables that are foreign */
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, false, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_MISC;
  			break;
***************
*** 3067,3073 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			pass = AT_PASS_ADD_INDEX;
  			break;
  		case AT_AddConstraint:	/* ADD CONSTRAINT */
! 			ATSimplePermissions(rel, ATT_TABLE);
  			/* Recursion occurs during execution phase */
  			/* No command-specific prep needed except saving recurse flag */
  			if (recurse)
--- 3069,3075 ----
  			pass = AT_PASS_ADD_INDEX;
  			break;
  		case AT_AddConstraint:	/* ADD CONSTRAINT */
! 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
  			/* Recursion occurs during execution phase */
  			/* No command-specific prep needed except saving recurse flag */
  			if (recurse)
***************
*** 3081,3087 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			pass = AT_PASS_ADD_CONSTR;
  			break;
  		case AT_DropConstraint:	/* DROP CONSTRAINT */
! 			ATSimplePermissions(rel, ATT_TABLE);
  			/* Recursion occurs during execution phase */
  			/* No command-specific prep needed except saving recurse flag */
  			if (recurse)
--- 3083,3089 ----
  			pass = AT_PASS_ADD_CONSTR;
  			break;
  		case AT_DropConstraint:	/* DROP CONSTRAINT */
! 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
  			/* Recursion occurs during execution phase */
  			/* No command-specific prep needed except saving recurse flag */
  			if (recurse)
***************
*** 3149,3159 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			pass = AT_PASS_MISC;
  			break;
  		case AT_AddInherit:		/* INHERIT */
! 			ATSimplePermissions(rel, ATT_TABLE);
  			/* This command never recurses */
  			ATPrepAddInherit(rel);
  			pass = AT_PASS_MISC;
  			break;
  		case AT_AlterConstraint:		/* ALTER CONSTRAINT */
  			ATSimplePermissions(rel, ATT_TABLE);
  			pass = AT_PASS_MISC;
--- 3151,3167 ----
  			pass = AT_PASS_MISC;
  			break;
  		case AT_AddInherit:		/* INHERIT */
! 			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;
***************
*** 3184,3190 **** 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 */
  			ATSimplePermissions(rel, ATT_TABLE);
--- 3192,3197 ----
***************
*** 4125,4131 **** ATWrongRelkindError(Relation rel, int allowed_targets)
   */
  static void
  ATSimpleRecursion(List **wqueue, Relation rel,
! 				  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode)
  {
  	/*
  	 * Propagate to children if desired.  Non-table relations never have
--- 4132,4139 ----
   */
  static void
  ATSimpleRecursion(List **wqueue, Relation rel,
! 				  AlterTableCmd *cmd, bool recurse,
! 				  bool include_foreign, LOCKMODE lockmode)
  {
  	/*
  	 * Propagate to children if desired.  Non-table relations never have
***************
*** 4153,4160 **** ATSimpleRecursion(List **wqueue, Relation rel,
  				continue;
  			/* find_all_inheritors already got lock */
  			childrel = relation_open(childrelid, NoLock);
! 			CheckTableNotInUse(childrel, "ALTER TABLE");
! 			ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
  			relation_close(childrel, NoLock);
  		}
  	}
--- 4161,4172 ----
  				continue;
  			/* find_all_inheritors already got lock */
  			childrel = relation_open(childrelid, NoLock);
! 			if (childrel->rd_rel->relkind != RELKIND_FOREIGN_TABLE
! 				|| include_foreign)
! 			{
! 				CheckTableNotInUse(childrel, "ALTER TABLE");
! 				ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
! 			}
  			relation_close(childrel, NoLock);
  		}
  	}
***************
*** 4444,4450 **** 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);
  
  	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
  
--- 4456,4462 ----
  
  	/* At top level, permission check was done in ATPrepCmd, else do it */
  	if (recursing)
! 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
  
  	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
  
***************
*** 4740,4745 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4752,4762 ----
  
  		/* find_inheritance_children already got lock */
  		childrel = heap_open(childrelid, NoLock);
+ 		if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE && isOid)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 					 errmsg("cannot add OID column to foreign table \"%s\"",
+ 							RelationGetRelationName(childrel))));
  		CheckTableNotInUse(childrel, "ALTER TABLE");
  
  		/* Find or create work queue entry for this table */
***************
*** 5340,5346 **** 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);
  
  	/*
  	 * get the number of the attribute
--- 5357,5363 ----
  
  	/* At top level, permission check was done in ATPrepCmd, else do it */
  	if (recursing)
! 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
  
  	/*
  	 * get the number of the attribute
***************
*** 5732,5738 **** 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);
  
  	/*
  	 * Call AddRelationNewConstraints to do the work, making sure it works on
--- 5749,5762 ----
  
  	/* At top level, permission check was done in ATPrepCmd, else do it */
  	if (recursing)
! 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
! 
! 	/* Don't allow ADD CONSTRAINT NOT VALID to be applied to foreign tables */
! 	if (tab->relkind == RELKIND_FOREIGN_TABLE &&
! 		constr->skip_validation && !recursing)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("NOT VALID is not supported on foreign tables")));
  
  	/*
  	 * Call AddRelationNewConstraints to do the work, making sure it works on
***************
*** 5743,5751 **** ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
  	 * omitted from the returned list, which is what we want: we do not need
  	 * to do any validation work.  That can only happen at child tables,
  	 * though, since we disallow merging at the top level.
  	 */
  	newcons = AddRelationNewConstraints(rel, NIL,
! 										list_make1(copyObject(constr)),
  										recursing,		/* allow_merge */
  										!recursing,		/* is_local */
  										is_readd);		/* is_internal */
--- 5767,5783 ----
  	 * omitted from the returned list, which is what we want: we do not need
  	 * to do any validation work.  That can only happen at child tables,
  	 * though, since we disallow merging at the top level.
+ 	 *
+ 	 * When propagating a NOT VALID option to children that are foreign tables,
+ 	 * we quietly ignore the option.  Note that this is safe because foreign
+ 	 * tables don't have any children.
  	 */
+ 	constr = (Constraint *) copyObject(constr);
+ 	if (tab->relkind == RELKIND_FOREIGN_TABLE &&
+ 		constr->skip_validation && recursing)
+ 		constr->skip_validation = false;
  	newcons = AddRelationNewConstraints(rel, NIL,
! 										list_make1(constr),
  										recursing,		/* allow_merge */
  										!recursing,		/* is_local */
  										is_readd);		/* is_internal */
***************
*** 7225,7231 **** ATExecDropConstraint(Relation rel, const char *constrName,
  
  	/* At top level, permission check was done in ATPrepCmd, else do it */
  	if (recursing)
! 		ATSimplePermissions(rel, ATT_TABLE);
  
  	conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
  
--- 7257,7263 ----
  
  	/* At top level, permission check was done in ATPrepCmd, else do it */
  	if (recursing)
! 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
  
  	conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
  
***************
*** 7560,7566 **** ATPrepAlterColumnType(List **wqueue,
  	 * alter would put them out of step.
  	 */
  	if (recurse)
! 		ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  	else if (!recursing &&
  			 find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
  		ereport(ERROR,
--- 7592,7601 ----
  	 * alter would put them out of step.
  	 */
  	if (recurse)
! 	{
! 		/* Recurse to child tables that are foreign, too */
! 		ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode);
! 	}
  	else if (!recursing &&
  			 find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
  		ereport(ERROR,
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 61,66 **** int			vacuum_multixact_freeze_table_age;
--- 61,67 ----
  
  /* A few variables that don't seem worth passing around as parameters */
  static MemoryContext vac_context = NULL;
+ static VacuumMode vac_mode;
  static BufferAccessStrategy vac_strategy;
  
  
***************
*** 146,151 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 147,166 ----
  										ALLOCSET_DEFAULT_MAXSIZE);
  
  	/*
+ 	 * Identify vacuum mode.  If relid is not InvalidOid, the caller should be
+ 	 * an autovacuum worker.  See the above comments.
+ 	 */
+ 	if (relid != InvalidOid)
+ 		vac_mode = VAC_MODE_AUTOVACUUM;
+ 	else
+ 	{
+ 		if (!vacstmt->relation)
+ 			vac_mode = VAC_MODE_ALL;
+ 		else
+ 			vac_mode = VAC_MODE_SINGLE;
+ 	}
+ 
+ 	/*
  	 * If caller didn't give us a buffer strategy object, make one in the
  	 * cross-transaction memory context.
  	 */
***************
*** 248,254 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
  					PushActiveSnapshot(GetTransactionSnapshot());
  				}
  
! 				analyze_rel(relid, vacstmt, vac_strategy);
  
  				if (use_own_xacts)
  				{
--- 263,269 ----
  					PushActiveSnapshot(GetTransactionSnapshot());
  				}
  
! 				analyze_rel(relid, vacstmt, vac_mode, vac_strategy);
  
  				if (use_own_xacts)
  				{
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1337,1347 **** 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.
  		 */
  		childrte = copyObject(rte);
  		childrte->relid = childOID;
  		childrte->inh = false;
  		childrte->requiredPerms = 0;
  		parse->rtable = lappend(parse->rtable, childrte);
--- 1337,1348 ----
  		/*
  		 * 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,
! 		 * 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);
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
***************
*** 16,21 ****
--- 16,22 ----
  
  #include <math.h>
  
+ #include "foreign/fdwapi.h"
  #include "miscadmin.h"
  #include "nodes/nodeFuncs.h"
  #include "optimizer/clauses.h"
***************
*** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 2063,2093 ----
  		case T_SubqueryScan:
  			return create_subqueryscan_path(root, rel, path->pathkeys,
  											required_outer);
+ 		case T_ForeignScan:
+ 			{
+ 				ForeignPath *newpath = NULL;
+ 
+ 				/* Let the FDW reparameterize the path node if possible */
+ 				if (rel->fdwroutine->ReparameterizeForeignPath != NULL)
+ 				{
+ 					Index		scan_relid = rel->relid;
+ 					RangeTblEntry *rte;
+ 
+ 					/* it should be a base rel... */
+ 					Assert(scan_relid > 0);
+ 					Assert(rel->rtekind == RTE_RELATION);
+ 					rte = planner_rt_fetch(scan_relid, root);
+ 					Assert(rte->rtekind == RTE_RELATION);
+ 
+ 					newpath =
+ 						rel->fdwroutine->ReparameterizeForeignPath(root,
+ 																   rel,
+ 																   rte->relid,
+ 														  (ForeignPath *) path,
+ 															   required_outer);
+ 				}
+ 				return (Path *) newpath;
+ 			}
  		default:
  			break;
  	}
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4209,4240 **** AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
  CreateForeignTableStmt:
  		CREATE FOREIGN TABLE qualified_name
  			'(' OptTableElementList ')'
! 			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.if_not_exists = false;
  					/* FDW-specific data */
! 					n->servername = $9;
! 					n->options = $10;
  					$$ = (Node *) n;
  				}
  		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
  			'(' OptTableElementList ')'
! 			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.if_not_exists = true;
  					/* FDW-specific data */
! 					n->servername = $12;
! 					n->options = $13;
  					$$ = (Node *) n;
  				}
  		;
--- 4209,4240 ----
  CreateForeignTableStmt:
  		CREATE FOREIGN TABLE qualified_name
  			'(' OptTableElementList ')'
! 			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 = $8;
  					n->base.if_not_exists = false;
  					/* FDW-specific data */
! 					n->servername = $10;
! 					n->options = $11;
  					$$ = (Node *) n;
  				}
  		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
  			'(' OptTableElementList ')'
! 			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 = $11;
  					n->base.if_not_exists = true;
  					/* FDW-specific data */
! 					n->servername = $13;
! 					n->options = $14;
  					$$ = (Node *) n;
  				}
  		;
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 515,526 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
  				break;
  
  			case CONSTR_CHECK:
- 				if (cxt->isforeign)
- 					ereport(ERROR,
- 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- 					errmsg("constraints are not supported on foreign tables"),
- 							 parser_errposition(cxt->pstate,
- 												constraint->location)));
  				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
  				break;
  
--- 515,520 ----
***************
*** 529,535 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
  				if (cxt->isforeign)
  					ereport(ERROR,
  							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					errmsg("constraints are not supported on foreign tables"),
  							 parser_errposition(cxt->pstate,
  												constraint->location)));
  				if (constraint->keys == NIL)
--- 523,529 ----
  				if (cxt->isforeign)
  					ereport(ERROR,
  							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					errmsg("primary key or unique constraints are not supported on foreign tables"),
  							 parser_errposition(cxt->pstate,
  												constraint->location)));
  				if (constraint->keys == NIL)
***************
*** 546,552 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
  				if (cxt->isforeign)
  					ereport(ERROR,
  							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					errmsg("constraints are not supported on foreign tables"),
  							 parser_errposition(cxt->pstate,
  												constraint->location)));
  
--- 540,546 ----
  				if (cxt->isforeign)
  					ereport(ERROR,
  							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					errmsg("foreign key constraints are not supported on foreign tables"),
  							 parser_errposition(cxt->pstate,
  												constraint->location)));
  
***************
*** 605,614 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
  static void
  transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
  {
! 	if (cxt->isforeign)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("constraints are not supported on foreign tables"),
  				 parser_errposition(cxt->pstate,
  									constraint->location)));
  
--- 599,612 ----
  static void
  transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
  {
! 	if (cxt->isforeign &&
! 		(constraint->contype == CONSTR_PRIMARY ||
! 		 constraint->contype == CONSTR_UNIQUE ||
! 		 constraint->contype == CONSTR_EXCLUSION ||
! 		 constraint->contype == CONSTR_FOREIGN))
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("primary key, unique, exclusion, or foreign key constraints are not supported on foreign tables"),
  				 parser_errposition(cxt->pstate,
  									constraint->location)));
  
*** a/src/include/commands/vacuum.h
--- b/src/include/commands/vacuum.h
***************
*** 140,145 **** extern int	vacuum_multixact_freeze_min_age;
--- 140,154 ----
  extern int	vacuum_multixact_freeze_table_age;
  
  
+ /* Possible modes for vacuum() */
+ typedef enum
+ {
+ 	VAC_MODE_ALL,				/* Vacuum/analyze all relations */
+ 	VAC_MODE_SINGLE,			/* Vacuum/analyze a specific relation */
+ 	VAC_MODE_AUTOVACUUM			/* Autovacuum worker */
+ } VacuumMode;
+ 
+ 
  /* in commands/vacuum.c */
  extern void vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
  	   BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel);
***************
*** 174,180 **** extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
  				BufferAccessStrategy bstrategy);
  
  /* in commands/analyze.c */
! extern void analyze_rel(Oid relid, VacuumStmt *vacstmt,
  			BufferAccessStrategy bstrategy);
  extern bool std_typanalyze(VacAttrStats *stats);
  extern double anl_random_fract(void);
--- 183,191 ----
  				BufferAccessStrategy bstrategy);
  
  /* in commands/analyze.c */
! extern void analyze_rel(Oid relid,
! 			VacuumStmt *vacstmt,
! 			VacuumMode vacmode,
  			BufferAccessStrategy bstrategy);
  extern bool std_typanalyze(VacAttrStats *stats);
  extern double anl_random_fract(void);
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
***************
*** 31,36 **** typedef void (*GetForeignPaths_function) (PlannerInfo *root,
--- 31,42 ----
  													  RelOptInfo *baserel,
  													  Oid foreigntableid);
  
+ typedef ForeignPath *(*ReparameterizeForeignPath_function) (PlannerInfo *root,
+ 														 RelOptInfo *baserel,
+ 														  Oid foreigntableid,
+ 															ForeignPath *path,
+ 													  Relids required_outer);
+ 
  typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
  														 RelOptInfo *baserel,
  														  Oid foreigntableid,
***************
*** 117,122 **** typedef struct FdwRoutine
--- 123,129 ----
  	/* Functions for scanning foreign tables */
  	GetForeignRelSize_function GetForeignRelSize;
  	GetForeignPaths_function GetForeignPaths;
+ 	ReparameterizeForeignPath_function ReparameterizeForeignPath;
  	GetForeignPlan_function GetForeignPlan;
  	BeginForeignScan_function BeginForeignScan;
  	IterateForeignScan_function IterateForeignScan;
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 750,765 **** CREATE TABLE use_ft1_column_type (x ft1);
  ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer;	-- ERROR
  ERROR:  cannot alter foreign table "ft1" because column "use_ft1_column_type.x" uses its row type
  DROP TABLE use_ft1_column_type;
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0); -- ERROR
! ERROR:  constraints are not supported on foreign tables
! LINE 1: ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c...
!                                     ^
  ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const;               -- ERROR
! ERROR:  "ft1" is not a table
  ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
! ERROR:  "ft1" is not a table
! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check;
! ERROR:  "ft1" is not a table
  ALTER FOREIGN TABLE ft1 SET WITH OIDS;                          -- ERROR
  ERROR:  "ft1" is not a table
  ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
--- 750,761 ----
  ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer;	-- ERROR
  ERROR:  cannot alter foreign table "ft1" because column "use_ft1_column_type.x" uses its row type
  DROP TABLE use_ft1_column_type;
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0);
  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 DROP CONSTRAINT ft1_c9_check;
  ALTER FOREIGN TABLE ft1 SET WITH OIDS;                          -- ERROR
  ERROR:  "ft1" is not a table
  ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 314,323 **** ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
  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 CONSTRAINT ft1_c9_check CHECK (c9 < 0); -- ERROR
  ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const;               -- ERROR
  ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check;
  ALTER FOREIGN TABLE ft1 SET WITH OIDS;                          -- ERROR
  ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
  ALTER FOREIGN TABLE ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@');
--- 314,323 ----
  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 CONSTRAINT ft1_c9_check CHECK (c9 < 0);
  ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const;               -- ERROR
  ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check;
  ALTER FOREIGN TABLE ft1 SET WITH OIDS;                          -- ERROR
  ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
  ALTER FOREIGN TABLE ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@');


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/plain] foreign_inherit-v9.patch (60.7K, ../53341F3C.2050609@lab.ntt.co.jp/2-foreign_inherit-v9.patch)
  download | inline diff:
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
***************
*** 117,122 **** static void fileGetForeignRelSize(PlannerInfo *root,
--- 117,127 ----
  static void fileGetForeignPaths(PlannerInfo *root,
  					RelOptInfo *baserel,
  					Oid foreigntableid);
+ static ForeignPath *fileReparameterizeForeignPath(PlannerInfo *root,
+ 												  RelOptInfo *baserel,
+ 												  Oid foreigntableid,
+ 												  ForeignPath *path,
+ 												  Relids required_outer);
  static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
  				   RelOptInfo *baserel,
  				   Oid foreigntableid,
***************
*** 145,151 **** static bool check_selective_binary_conversion(RelOptInfo *baserel,
  static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
  			  FileFdwPlanState *fdw_private);
  static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
! 			   FileFdwPlanState *fdw_private,
  			   Cost *startup_cost, Cost *total_cost);
  static int file_acquire_sample_rows(Relation onerel, int elevel,
  						 HeapTuple *rows, int targrows,
--- 150,156 ----
  static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
  			  FileFdwPlanState *fdw_private);
  static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
! 			   FileFdwPlanState *fdw_private, List *join_conds,
  			   Cost *startup_cost, Cost *total_cost);
  static int file_acquire_sample_rows(Relation onerel, int elevel,
  						 HeapTuple *rows, int targrows,
***************
*** 163,168 **** file_fdw_handler(PG_FUNCTION_ARGS)
--- 168,174 ----
  
  	fdwroutine->GetForeignRelSize = fileGetForeignRelSize;
  	fdwroutine->GetForeignPaths = fileGetForeignPaths;
+ 	fdwroutine->ReparameterizeForeignPath = fileReparameterizeForeignPath;
  	fdwroutine->GetForeignPlan = fileGetForeignPlan;
  	fdwroutine->ExplainForeignScan = fileExplainForeignScan;
  	fdwroutine->BeginForeignScan = fileBeginForeignScan;
***************
*** 517,523 **** fileGetForeignPaths(PlannerInfo *root,
  										  (Node *) columns));
  
  	/* Estimate costs */
! 	estimate_costs(root, baserel, fdw_private,
  				   &startup_cost, &total_cost);
  
  	/*
--- 523,530 ----
  										  (Node *) columns));
  
  	/* Estimate costs */
! 	estimate_costs(root, baserel,
! 				   fdw_private, NIL,
  				   &startup_cost, &total_cost);
  
  	/*
***************
*** 542,547 **** fileGetForeignPaths(PlannerInfo *root,
--- 549,589 ----
  }
  
  /*
+  * fileReparameterizeForeignPath
+  *		Attempt to modify a given path to have greater parameterization
+  */
+ static ForeignPath *
+ fileReparameterizeForeignPath(PlannerInfo *root,
+ 							  RelOptInfo *baserel,
+ 							  Oid foreigntableid,
+ 							  ForeignPath *path,
+ 							  Relids required_outer)
+ {
+ 	ParamPathInfo *param_info;
+ 	FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private;
+ 	Cost		startup_cost;
+ 	Cost		total_cost;
+ 
+ 	/* Get the ParamPathInfo */
+ 	param_info = get_baserel_parampathinfo(root, baserel, required_outer);
+ 
+ 	/* Redo the cost estimates */
+ 	estimate_costs(root, baserel,
+ 				   fdw_private,
+ 				   param_info->ppi_clauses,
+ 				   &startup_cost, &total_cost);
+ 
+ 	/* Make and return the new path */
+ 	return create_foreignscan_path(root, baserel,
+ 								   param_info->ppi_rows,
+ 								   startup_cost,
+ 								   total_cost,
+ 								   NIL,		/* no pathkeys */
+ 								   required_outer,
+ 								   path->fdw_private);
+ }
+ 
+ /*
   * fileGetForeignPlan
   *		Create a ForeignScan plan node for scanning the foreign table
   */
***************
*** 970,981 **** estimate_size(PlannerInfo *root, RelOptInfo *baserel,
   */
  static void
  estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
! 			   FileFdwPlanState *fdw_private,
  			   Cost *startup_cost, Cost *total_cost)
  {
  	BlockNumber pages = fdw_private->pages;
  	double		ntuples = fdw_private->ntuples;
  	Cost		run_cost = 0;
  	Cost		cpu_per_tuple;
  
  	/*
--- 1012,1024 ----
   */
  static void
  estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
! 			   FileFdwPlanState *fdw_private, List *join_conds,
  			   Cost *startup_cost, Cost *total_cost)
  {
  	BlockNumber pages = fdw_private->pages;
  	double		ntuples = fdw_private->ntuples;
  	Cost		run_cost = 0;
+ 	QualCost	join_cost;
  	Cost		cpu_per_tuple;
  
  	/*
***************
*** 986,993 **** estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
  	 */
  	run_cost += seq_page_cost * pages;
  
! 	*startup_cost = baserel->baserestrictcost.startup;
! 	cpu_per_tuple = cpu_tuple_cost * 10 + baserel->baserestrictcost.per_tuple;
  	run_cost += cpu_per_tuple * ntuples;
  	*total_cost = *startup_cost + run_cost;
  }
--- 1029,1039 ----
  	 */
  	run_cost += seq_page_cost * pages;
  
! 	cost_qual_eval(&join_cost, join_conds, root);
! 	*startup_cost =
! 		(baserel->baserestrictcost.startup + join_cost.startup);
! 	cpu_per_tuple = cpu_tuple_cost * 10 +
! 		(baserel->baserestrictcost.per_tuple + join_cost.per_tuple);
  	run_cost += cpu_per_tuple * ntuples;
  	*total_cost = *startup_cost + run_cost;
  }
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 241,246 **** static void postgresGetForeignRelSize(PlannerInfo *root,
--- 241,251 ----
  static void postgresGetForeignPaths(PlannerInfo *root,
  						RelOptInfo *baserel,
  						Oid foreigntableid);
+ static ForeignPath *postgresReparameterizeForeignPath(PlannerInfo *root,
+ 													  RelOptInfo *baserel,
+ 													  Oid foreigntableid,
+ 													  ForeignPath *path,
+ 													  Relids required_outer);
  static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
  					   RelOptInfo *baserel,
  					   Oid foreigntableid,
***************
*** 342,347 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 347,353 ----
  	/* Functions for scanning foreign tables */
  	routine->GetForeignRelSize = postgresGetForeignRelSize;
  	routine->GetForeignPaths = postgresGetForeignPaths;
+ 	routine->ReparameterizeForeignPath = postgresReparameterizeForeignPath;
  	routine->GetForeignPlan = postgresGetForeignPlan;
  	routine->BeginForeignScan = postgresBeginForeignScan;
  	routine->IterateForeignScan = postgresIterateForeignScan;
***************
*** 729,734 **** postgresGetForeignPaths(PlannerInfo *root,
--- 735,782 ----
  }
  
  /*
+  * postgresReparameterizeForeignPath
+  *		Attempt to modify a given path to have greater parameterization
+  */
+ static ForeignPath *
+ postgresReparameterizeForeignPath(PlannerInfo *root,
+ 								  RelOptInfo *baserel,
+ 								  Oid foreigntableid,
+ 								  ForeignPath *path,
+ 								  Relids required_outer)
+ {
+ 	ParamPathInfo *param_info;
+ 	double		rows;
+ 	int			width;
+ 	Cost		startup_cost;
+ 	Cost		total_cost;
+ 
+ 	/* Get the ParamPathInfo */
+ 	param_info = get_baserel_parampathinfo(root, baserel, required_outer);
+ 
+ 	/* Redo the cost estimates */
+ 	estimate_path_cost_size(root, baserel,
+ 							param_info->ppi_clauses,
+ 							&rows, &width,
+ 							&startup_cost, &total_cost);
+ 
+ 	/*
+ 	 * ppi_rows currently won't get looked at by anything, but still we
+ 	 * may as well ensure that it matches our idea of the rowcount.
+ 	 */
+ 	param_info->ppi_rows = rows;
+ 
+ 	/* Make and return the new path */
+ 	return create_foreignscan_path(root, baserel,
+ 								   rows,
+ 								   startup_cost,
+ 								   total_cost,
+ 								   NIL,		/* no pathkeys */
+ 								   required_outer,
+ 								   NIL);	/* no fdw_private list */
+ }
+ 
+ /*
   * postgresGetForeignPlan
   *		Create ForeignScan plan node which implements selected best path
   */
***************
*** 1775,1785 **** estimate_path_cost_size(PlannerInfo *root,
  	}
  	else
  	{
! 		/*
! 		 * We don't support join conditions in this mode (hence, no
! 		 * parameterized paths can be made).
! 		 */
! 		Assert(join_conds == NIL);
  
  		/* Use rows/width estimates made by set_baserel_size_estimates. */
  		rows = baserel->rows;
--- 1823,1830 ----
  	}
  	else
  	{
! 		Selectivity join_sel;
! 		QualCost	join_cost;
  
  		/* Use rows/width estimates made by set_baserel_size_estimates. */
  		rows = baserel->rows;
***************
*** 1792,1808 **** estimate_path_cost_size(PlannerInfo *root,
  		retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel);
  		retrieved_rows = Min(retrieved_rows, baserel->tuples);
  
  		/*
  		 * Cost as though this were a seqscan, which is pessimistic.  We
! 		 * effectively imagine the local_conds are being evaluated remotely,
! 		 * too.
  		 */
  		startup_cost = 0;
  		run_cost = 0;
  		run_cost += seq_page_cost * baserel->pages;
  
! 		startup_cost += baserel->baserestrictcost.startup;
! 		cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
  		run_cost += cpu_per_tuple * baserel->tuples;
  
  		total_cost = startup_cost + run_cost;
--- 1837,1865 ----
  		retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel);
  		retrieved_rows = Min(retrieved_rows, baserel->tuples);
  
+ 		/* Factor in the selectivity of the join_conds */
+ 		join_sel = clauselist_selectivity(root,
+ 										  join_conds,
+ 										  baserel->relid,
+ 										  JOIN_INNER,
+ 										  NULL);
+ 
+ 		rows = clamp_row_est(rows * join_sel);
+ 
  		/*
  		 * Cost as though this were a seqscan, which is pessimistic.  We
! 		 * effectively imagine the local_conds and join_conds are being
! 		 * evaluated remotely, too.
  		 */
  		startup_cost = 0;
  		run_cost = 0;
  		run_cost += seq_page_cost * baserel->pages;
  
! 		cost_qual_eval(&join_cost, join_conds, root);
! 		startup_cost +=
! 			(baserel->baserestrictcost.startup + join_cost.startup);
! 		cpu_per_tuple = cpu_tuple_cost +
! 			(baserel->baserestrictcost.per_tuple + join_cost.per_tuple);
  		run_cost += cpu_per_tuple * baserel->tuples;
  
  		total_cost = startup_cost + run_cost;
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 162,167 **** GetForeignPaths (PlannerInfo *root,
--- 162,198 ----
  
      <para>
  <programlisting>
+ ForeignPath *
+ ReparameterizeForeignPath (PlannerInfo *root,
+                            RelOptInfo *baserel,
+                            Oid foreigntableid,
+                            ForeignPath *path,
+                            Relids required_outer);
+ </programlisting>
+ 
+      Create an access path for a scan on a foreign table using join
+      clauses, which is called a <quote>parameterized path</>.
+      This is called during query planning.
+      The parameters are as for <function>GetForeignRelSize</>, plus
+      the <structname>ForeignPath</> (previously produced by
+      <function>GetForeignPaths</>), and the IDs of all other tables
+      that provide the join clauses.
+     </para>
+ 
+     <para>
+      This function must generate a parameterized path for a scan on the
+      foreign table.  The parameterized path must contain cost estimates,
+      and can contain any FDW-private information that is needed to identify
+      the specific scan method intended.  Unlike the other scan-related
+      functions, this function is optional.
+     </para>
+ 
+     <para>
+      See <xref linkend="fdw-planning"> for additional information.
+     </para>
+ 
+     <para>
+ <programlisting>
  ForeignScan *
  GetForeignPlan (PlannerInfo *root,
                  RelOptInfo *baserel,
***************
*** 804,813 **** GetForeignServerByName(const char *name, bool missing_ok);
  
      <para>
       The FDW callback functions <function>GetForeignRelSize</>,
!      <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
!      <function>PlanForeignModify</> must fit into the workings of the
!      <productname>PostgreSQL</> planner.  Here are some notes about what
!      they must do.
      </para>
  
      <para>
--- 835,844 ----
  
      <para>
       The FDW callback functions <function>GetForeignRelSize</>,
!      <function>GetForeignPaths</>, <function>ReparameterizeForeignPath</>,
!      <function>GetForeignPlan</>, and <function>PlanForeignModify</> must fit
!      into the workings of the <productname>PostgreSQL</> planner.  Here are
!      some notes about what they must do.
      </para>
  
      <para>
***************
*** 837,850 **** GetForeignServerByName(const char *name, bool missing_ok);
       to initialize it to NULL when the <literal>baserel</> node is created.
       It is useful for passing information forward from
       <function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
!      <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
!      avoiding recalculation.
      </para>
  
      <para>
!      <function>GetForeignPaths</> can identify the meaning of different
!      access paths by storing private information in the
!      <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
       <structfield>fdw_private</> is declared as a <type>List</> pointer, but
       could actually contain anything since the core planner does not touch
       it.  However, best practice is to use a representation that's dumpable
--- 868,884 ----
       to initialize it to NULL when the <literal>baserel</> node is created.
       It is useful for passing information forward from
       <function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
!      <function>GetForeignPaths</> to <function>ReparameterizeForeignPath</>
!      and/or
!      <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
!      to <function>GetForeignPlan</>, thereby avoiding recalculation.
      </para>
  
      <para>
!      <function>GetForeignPaths</> or <function>ReparameterizeForeignPath</>
!      can identify the meaning of different access paths by storing private
!      information in the <structfield>fdw_private</> field of
!      <structname>ForeignPath</> nodes.
       <structfield>fdw_private</> is declared as a <type>List</> pointer, but
       could actually contain anything since the core planner does not touch
       it.  However, best practice is to use a representation that's dumpable
***************
*** 887,896 **** GetForeignServerByName(const char *name, bool missing_ok);
       <replaceable>sub_expression</>, which it determines can be executed on
       the remote server given the locally-evaluated value of the
       <replaceable>sub_expression</>.  The actual identification of such a
!      clause should happen during <function>GetForeignPaths</>, since it would
!      affect the cost estimate for the path.  The path's
!      <structfield>fdw_private</> field would probably include a pointer to
!      the identified clause's <structname>RestrictInfo</> node.  Then
       <function>GetForeignPlan</> would remove that clause from scan_clauses,
       but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
       to ensure that it gets massaged into executable form.  It would probably
--- 921,931 ----
       <replaceable>sub_expression</>, which it determines can be executed on
       the remote server given the locally-evaluated value of the
       <replaceable>sub_expression</>.  The actual identification of such a
!      clause should happen during <function>GetForeignPaths</> or
!      <function>ReparameterizeForeignPath</>, since it would affect the cost
!      estimate for the path.  The path's <structfield>fdw_private</> field
!      would probably include a pointer to the identified clause's
!      <structname>RestrictInfo</> node.  Then
       <function>GetForeignPlan</> would remove that clause from scan_clauses,
       but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
       to ensure that it gets massaged into executable form.  It would probably
*** a/doc/src/sgml/ref/alter_foreign_table.sgml
--- b/doc/src/sgml/ref/alter_foreign_table.sgml
***************
*** 42,47 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 42,51 ----
      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> OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
+     ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+     DROP CONSTRAINT [ IF EXISTS ]  <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
+     INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
+     NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
      OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
      OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ])
  </synopsis>
***************
*** 149,154 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 153,202 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+     <listitem>
+      <para>
+       This form adds a new constraint to a table using the same syntax as
+       <xref linkend="SQL-CREATEFOREIGNTABLE">.
+       Unlike the case when adding a constraint to a regular table, nothing happens
+       to the underlying storage: this action simply declares that
+       some new constraint holds for all rows in the foreign table.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
+     <listitem>
+      <para>
+       This form drops the specified constraint on a table.
+       If <literal>IF EXISTS</literal> is specified and the constraint
+       does not exist, no error is thrown. In this case a notice is issued instead.
+      </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.  The parent table must be an ordinary table.
+      </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>
       <para>
***************
*** 270,275 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 318,341 ----
       </varlistentry>
  
       <varlistentry>
+       <term><replaceable class="PARAMETER">table_constraint</replaceable></term>
+       <listitem>
+        <para>
+         New table constraint for the table.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
+       <term><replaceable class="PARAMETER">constraint_name</replaceable></term>
+       <listitem>
+        <para>
+         Name of an existing constraint to drop.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+      <varlistentry>
        <term><literal>CASCADE</literal></term>
        <listitem>
         <para>
***************
*** 290,295 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 356,372 ----
       </varlistentry>
  
       <varlistentry>
+       <term><replaceable class="PARAMETER">parent_name</replaceable></term>
+       <listitem>
+        <para>
+         A parent table to associate or de-associate with this foreign table.
+         The parent table must be an ordinary table.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+ 
+      <varlistentry>
        <term><replaceable class="PARAMETER">new_owner</replaceable></term>
        <listitem>
         <para>
***************
*** 306,312 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
         </para>
        </listitem>
       </varlistentry>
-     </variablelist>
   </refsect1>
  
   <refsect1>
--- 383,388 ----
***************
*** 319,328 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
     <para>
      Consistency with the foreign server is not checked when a column is added
      or removed with <literal>ADD COLUMN</literal> or
!     <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint is
!     added, or a column type is changed with <literal>SET DATA TYPE</>.  It is
!     the user's responsibility to ensure that the table definition matches the
!     remote side.
     </para>
  
     <para>
--- 395,404 ----
     <para>
      Consistency with the foreign server is not checked when a column is added
      or removed with <literal>ADD COLUMN</literal> or
!     <literal>DROP COLUMN</literal>, a <literal>NOT NULL</> constraint or
!     <literal>CHECK</> constraint is added, or a column type is changed with
!     <literal>SET DATA TYPE</>.  It is the user's responsibility to ensure that
!     the table definition matches the remote side.
     </para>
  
     <para>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 937,942 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 937,949 ----
     </para>
  
     <para>
+     If a table has any descendant tables that are foreign, a recursive
+     <literal>SET STORAGE</literal> operation will be rejected since it
+     is not permitted to add an <literal>oid</literal> system column to
+     foreign tables.
+    </para>
+ 
+    <para>
      The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>,
      and <literal>TABLESPACE</> actions never recurse to descendant tables;
      that is, they always act as though <literal>ONLY</> were specified.
***************
*** 945,950 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 952,970 ----
     </para>
  
     <para>
+     When adding a <literal>CHECK</> constraint with the <literal>NOT VALID
+     </literal> option recursively, an inherited constraint on a descendant
+     table that is foreign will be marked valid without checking
+     consistency with the foreign server.
+    </para>
+ 
+    <para>
+     A recursive <literal>SET STORAGE</literal> operation will make the
+     storage mode for a descendant table's column unchanged if the table is
+     foreign.
+    </para>
+ 
+    <para>
      Changing any part of a system catalog table is not permitted.
     </para>
  
*** a/doc/src/sgml/ref/analyze.sgml
--- b/doc/src/sgml/ref/analyze.sgml
***************
*** 200,205 **** ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
--- 200,212 ----
    </para>
  
    <para>
+     The inheritance statistics for a parent table that contains one or more
+     children that are foreign tables are collected only when explicitly
+     selected.  If the foreign table's wrapper does not support
+     <command>ANALYZE</command>, the command prints a warning and does nothing.
+   </para>
+ 
+   <para>
      If the table being analyzed is completely empty, <command>ANALYZE</command>
      will not record new statistics for that table.  Any existing statistics
      will be retained.
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 19,27 ****
   <refsynopsisdiv>
  <synopsis>
  CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
!     <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
      [, ... ]
  ] )
    SERVER <replaceable class="parameter">server_name</replaceable>
  [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
  
--- 19,29 ----
   <refsynopsisdiv>
  <synopsis>
  CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> ( [
!   { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] [ COLLATE <replaceable>collation</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
!     | <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>' [, ... ] ) ]
  
***************
*** 30,36 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 32,44 ----
  [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
  { NOT NULL |
    NULL |
+   CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] |
    DEFAULT <replaceable>default_expr</replaceable> }
+ 
+ <phrase>and <replaceable class="PARAMETER">table_constraint</replaceable> is:</phrase>
+ 
+ [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
+ { CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] }
  </synopsis>
   </refsynopsisdiv>
  
***************
*** 138,143 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 146,177 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] </literal></term>
+     <listitem>
+      <para>
+       The <literal>CHECK</> clause specifies an expression producing a
+       Boolean result which each row must satisfy.
+       Expressions evaluating to TRUE or UNKNOWN succeed.
+       A check constraint specified as a column constraint should
+       reference that column's value only, while an expression
+       appearing in a table constraint can reference multiple columns.
+      </para>
+ 
+      <para>
+       Currently, <literal>CHECK</literal> expressions cannot contain
+       subqueries nor refer to variables other than columns of the
+       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>
+ 
+    <varlistentry>
      <term><literal>DEFAULT
      <replaceable>default_expr</replaceable></literal></term>
      <listitem>
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 193,210 ----
     </varlistentry>
  
     <varlistentry>
+     <term><replaceable class="PARAMETER">parent_table</replaceable></term>
+     <listitem>
+      <para>
+       The name of an existing table from which the new foreign table
+       automatically inherits all columns.  The specified parent table
+       must be an ordinary table.  See <xref linkend="ddl-inherit"> for the
+       details of table inheritance.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><replaceable class="PARAMETER">server_name</replaceable></term>
      <listitem>
       <para>
***************
*** 187,192 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 233,249 ----
  
   </refsect1>
  
+  <refsect1>
+   <title>Notes</title>
+ 
+    <para>
+     Constraints on foreign tables are not enforced on insert or update.
+     Those definitions simply declare the constraints hold for all rows
+     in the foreign tables.  It is the user's responsibility to ensure
+     that those definitions match the remote side.  Such constraints are
+     used for some kind of query optimization such as constraint exclusion.
+    </para>
+  </refsect1>
  
   <refsect1 id="SQL-CREATEFOREIGNTABLE-examples">
    <title>Examples</title>
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 82,87 **** int			default_statistics_target = 100;
--- 82,88 ----
  
  /* A few variables that don't seem worth passing around as parameters */
  static MemoryContext anl_context = NULL;
+ static VacuumMode vac_mode;
  static BufferAccessStrategy vac_strategy;
  
  
***************
*** 115,121 **** static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
   *	analyze_rel() -- analyze one relation
   */
  void
! analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
  {
  	Relation	onerel;
  	int			elevel;
--- 116,125 ----
   *	analyze_rel() -- analyze one relation
   */
  void
! analyze_rel(Oid relid,
! 			VacuumStmt *vacstmt,
! 			VacuumMode vacmode,
! 			BufferAccessStrategy bstrategy)
  {
  	Relation	onerel;
  	int			elevel;
***************
*** 129,134 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
--- 133,139 ----
  		elevel = DEBUG2;
  
  	/* Set up static variables */
+ 	vac_mode = vacmode;
  	vac_strategy = bstrategy;
  
  	/*
***************
*** 1452,1457 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1457,1463 ----
  {
  	List	   *tableOIDs;
  	Relation   *rels;
+ 	AcquireSampleRowsFunc *acquirefunc;
  	double	   *relblocks;
  	double		totalblocks;
  	int			numrows,
***************
*** 1486,1491 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1492,1499 ----
  	 * BlockNumber, so we use double arithmetic.
  	 */
  	rels = (Relation *) palloc(list_length(tableOIDs) * sizeof(Relation));
+ 	acquirefunc = (AcquireSampleRowsFunc *) palloc(list_length(tableOIDs)
+ 											* sizeof(AcquireSampleRowsFunc));
  	relblocks = (double *) palloc(list_length(tableOIDs) * sizeof(double));
  	totalblocks = 0;
  	nrels = 0;
***************
*** 1507,1513 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
  		}
  
  		rels[nrels] = childrel;
! 		relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
  		totalblocks += relblocks[nrels];
  		nrels++;
  	}
--- 1515,1554 ----
  		}
  
  		rels[nrels] = childrel;
! 
! 		if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
! 		{
! 			FdwRoutine *fdwroutine;
! 			BlockNumber relpages = 0;
! 			bool		ok = false;
! 
! 			/* Ignore unless analyzing a specific inheritance tree */
! 			if (vac_mode != VAC_MODE_SINGLE)
! 				return 0;
! 
! 			/* Check whether the FDW supports analysis */
! 			fdwroutine = GetFdwRoutineForRelation(childrel, false);
! 			if (fdwroutine->AnalyzeForeignTable != NULL)
! 				ok = fdwroutine->AnalyzeForeignTable(childrel,
! 													 &acquirefunc[nrels],
! 													 &relpages);
! 			if (!ok)
! 			{
! 				/* Give up if the FDW doesn't support analysis */
! 				ereport(WARNING,
! 						(errmsg("skipping analyze of inheritance tree \"%s\" --- cannot analyze foreign table \"%s\"",
! 								RelationGetRelationName(onerel),
! 								RelationGetRelationName(childrel))));
! 				return 0;
! 			}
! 			relblocks[nrels] = (double) relpages;
! 		}
! 		else
! 		{
! 			acquirefunc[nrels] = acquire_sample_rows;
! 			relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
! 		}
! 
  		totalblocks += relblocks[nrels];
  		nrels++;
  	}
***************
*** 1525,1530 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1566,1572 ----
  	{
  		Relation	childrel = rels[i];
  		double		childblocks = relblocks[i];
+ 		AcquireSampleRowsFunc childacquirefunc = acquirefunc[i];
  
  		if (childblocks > 0)
  		{
***************
*** 1540,1551 **** 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);
  
  				/* We may need to convert from child's rowtype to parent's */
  				if (childrows > 0 &&
--- 1582,1593 ----
  							tdrows;
  
  				/* Fetch a random sample of the child's rows */
! 				childrows = childacquirefunc(childrel,
! 											 elevel,
! 											 rows + numrows,
! 											 childtargrows,
! 											 &trows,
! 											 &tdrows);
  
  				/* We may need to convert from child's rowtype to parent's */
  				if (childrows > 0 &&
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 311,317 **** static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
  static void ATSimplePermissions(Relation rel, int allowed_targets);
  static void ATWrongRelkindError(Relation rel, int allowed_targets);
  static void ATSimpleRecursion(List **wqueue, Relation rel,
! 				  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode);
  static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
  					  LOCKMODE lockmode);
  static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
--- 311,318 ----
  static void ATSimplePermissions(Relation rel, int allowed_targets);
  static void ATWrongRelkindError(Relation rel, int allowed_targets);
  static void ATSimpleRecursion(List **wqueue, Relation rel,
! 				  AlterTableCmd *cmd, bool recurse,
! 				  bool include_foreign, LOCKMODE lockmode);
  static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
  					  LOCKMODE lockmode);
  static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
***************
*** 467,476 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
  				 errmsg("ON COMMIT can only be used on temporary tables")));
- 	if (stmt->constraints != NIL && relkind == RELKIND_FOREIGN_TABLE)
- 		ereport(ERROR,
- 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- 				 errmsg("constraints are not supported on foreign tables")));
  
  	/*
  	 * Look up the namespace in which we are supposed to create the relation,
--- 468,473 ----
***************
*** 3019,3042 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			 * rules.
  			 */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  			/* No command-specific prep needed */
  			pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
  			break;
  		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_DROP;
  			break;
  		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_ADD_CONSTR;
  			break;
  		case AT_SetStatistics:	/* ALTER COLUMN SET STATISTICS */
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  			/* Performs own permission checks */
  			ATPrepSetStatistics(rel, cmd->name, cmd->def, lockmode);
  			pass = AT_PASS_MISC;
--- 3016,3043 ----
  			 * rules.
  			 */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
! 			/* Recurse to child tables that are foreign, too */
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode);
  			/* No command-specific prep needed */
  			pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
  			break;
  		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
! 			/* Recurse to child tables that are foreign, too */
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_DROP;
  			break;
  		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
! 			/* Recurse to child tables that are foreign, too */
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_ADD_CONSTR;
  			break;
  		case AT_SetStatistics:	/* ALTER COLUMN SET STATISTICS */
! 			/* Recurse to child tables that are foreign, too */
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode);
  			/* Performs own permission checks */
  			ATPrepSetStatistics(rel, cmd->name, cmd->def, lockmode);
  			pass = AT_PASS_MISC;
***************
*** 3049,3055 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			break;
  		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_MISC;
  			break;
--- 3050,3057 ----
  			break;
  		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
  			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
! 			/* Don't recurse to child tables that are foreign */
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, false, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_MISC;
  			break;
***************
*** 3067,3073 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			pass = AT_PASS_ADD_INDEX;
  			break;
  		case AT_AddConstraint:	/* ADD CONSTRAINT */
! 			ATSimplePermissions(rel, ATT_TABLE);
  			/* Recursion occurs during execution phase */
  			/* No command-specific prep needed except saving recurse flag */
  			if (recurse)
--- 3069,3075 ----
  			pass = AT_PASS_ADD_INDEX;
  			break;
  		case AT_AddConstraint:	/* ADD CONSTRAINT */
! 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
  			/* Recursion occurs during execution phase */
  			/* No command-specific prep needed except saving recurse flag */
  			if (recurse)
***************
*** 3081,3087 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			pass = AT_PASS_ADD_CONSTR;
  			break;
  		case AT_DropConstraint:	/* DROP CONSTRAINT */
! 			ATSimplePermissions(rel, ATT_TABLE);
  			/* Recursion occurs during execution phase */
  			/* No command-specific prep needed except saving recurse flag */
  			if (recurse)
--- 3083,3089 ----
  			pass = AT_PASS_ADD_CONSTR;
  			break;
  		case AT_DropConstraint:	/* DROP CONSTRAINT */
! 			ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
  			/* Recursion occurs during execution phase */
  			/* No command-specific prep needed except saving recurse flag */
  			if (recurse)
***************
*** 3149,3159 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			pass = AT_PASS_MISC;
  			break;
  		case AT_AddInherit:		/* INHERIT */
! 			ATSimplePermissions(rel, ATT_TABLE);
  			/* This command never recurses */
  			ATPrepAddInherit(rel);
  			pass = AT_PASS_MISC;
  			break;
  		case AT_AlterConstraint:		/* ALTER CONSTRAINT */
  			ATSimplePermissions(rel, ATT_TABLE);
  			pass = AT_PASS_MISC;
--- 3151,3167 ----
  			pass = AT_PASS_MISC;
  			break;
  		case AT_AddInherit:		/* INHERIT */
! 			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;
***************
*** 3184,3190 **** 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 */
  			ATSimplePermissions(rel, ATT_TABLE);
--- 3192,3197 ----
***************
*** 4125,4131 **** ATWrongRelkindError(Relation rel, int allowed_targets)
   */
  static void
  ATSimpleRecursion(List **wqueue, Relation rel,
! 				  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode)
  {
  	/*
  	 * Propagate to children if desired.  Non-table relations never have
--- 4132,4139 ----
   */
  static void
  ATSimpleRecursion(List **wqueue, Relation rel,
! 				  AlterTableCmd *cmd, bool recurse,
! 				  bool include_foreign, LOCKMODE lockmode)
  {
  	/*
  	 * Propagate to children if desired.  Non-table relations never have
***************
*** 4153,4160 **** ATSimpleRecursion(List **wqueue, Relation rel,
  				continue;
  			/* find_all_inheritors already got lock */
  			childrel = relation_open(childrelid, NoLock);
! 			CheckTableNotInUse(childrel, "ALTER TABLE");
! 			ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
  			relation_close(childrel, NoLock);
  		}
  	}
--- 4161,4172 ----
  				continue;
  			/* find_all_inheritors already got lock */
  			childrel = relation_open(childrelid, NoLock);
! 			if (childrel->rd_rel->relkind != RELKIND_FOREIGN_TABLE
! 				|| include_foreign)
! 			{
! 				CheckTableNotInUse(childrel, "ALTER TABLE");
! 				ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
! 			}
  			relation_close(childrel, NoLock);
  		}
  	}
***************
*** 4444,4450 **** 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);
  
  	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
  
--- 4456,4462 ----
  
  	/* At top level, permission check was done in ATPrepCmd, else do it */
  	if (recursing)
! 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
  
  	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
  
***************
*** 4740,4745 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4752,4762 ----
  
  		/* find_inheritance_children already got lock */
  		childrel = heap_open(childrelid, NoLock);
+ 		if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE && isOid)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 					 errmsg("cannot add OID column to foreign table \"%s\"",
+ 							RelationGetRelationName(childrel))));
  		CheckTableNotInUse(childrel, "ALTER TABLE");
  
  		/* Find or create work queue entry for this table */
***************
*** 5340,5346 **** 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);
  
  	/*
  	 * get the number of the attribute
--- 5357,5363 ----
  
  	/* At top level, permission check was done in ATPrepCmd, else do it */
  	if (recursing)
! 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
  
  	/*
  	 * get the number of the attribute
***************
*** 5732,5738 **** 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);
  
  	/*
  	 * Call AddRelationNewConstraints to do the work, making sure it works on
--- 5749,5762 ----
  
  	/* At top level, permission check was done in ATPrepCmd, else do it */
  	if (recursing)
! 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
! 
! 	/* Don't allow ADD CONSTRAINT NOT VALID to be applied to foreign tables */
! 	if (tab->relkind == RELKIND_FOREIGN_TABLE &&
! 		constr->skip_validation && !recursing)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("NOT VALID is not supported on foreign tables")));
  
  	/*
  	 * Call AddRelationNewConstraints to do the work, making sure it works on
***************
*** 5743,5751 **** ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
  	 * omitted from the returned list, which is what we want: we do not need
  	 * to do any validation work.  That can only happen at child tables,
  	 * though, since we disallow merging at the top level.
  	 */
  	newcons = AddRelationNewConstraints(rel, NIL,
! 										list_make1(copyObject(constr)),
  										recursing,		/* allow_merge */
  										!recursing,		/* is_local */
  										is_readd);		/* is_internal */
--- 5767,5783 ----
  	 * omitted from the returned list, which is what we want: we do not need
  	 * to do any validation work.  That can only happen at child tables,
  	 * though, since we disallow merging at the top level.
+ 	 *
+ 	 * When propagating a NOT VALID option to children that are foreign tables,
+ 	 * we quietly ignore the option.  Note that this is safe because foreign
+ 	 * tables don't have any children.
  	 */
+ 	constr = (Constraint *) copyObject(constr);
+ 	if (tab->relkind == RELKIND_FOREIGN_TABLE &&
+ 		constr->skip_validation && recursing)
+ 		constr->skip_validation = false;
  	newcons = AddRelationNewConstraints(rel, NIL,
! 										list_make1(constr),
  										recursing,		/* allow_merge */
  										!recursing,		/* is_local */
  										is_readd);		/* is_internal */
***************
*** 7225,7231 **** ATExecDropConstraint(Relation rel, const char *constrName,
  
  	/* At top level, permission check was done in ATPrepCmd, else do it */
  	if (recursing)
! 		ATSimplePermissions(rel, ATT_TABLE);
  
  	conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
  
--- 7257,7263 ----
  
  	/* At top level, permission check was done in ATPrepCmd, else do it */
  	if (recursing)
! 		ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
  
  	conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
  
***************
*** 7560,7566 **** ATPrepAlterColumnType(List **wqueue,
  	 * alter would put them out of step.
  	 */
  	if (recurse)
! 		ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  	else if (!recursing &&
  			 find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
  		ereport(ERROR,
--- 7592,7601 ----
  	 * alter would put them out of step.
  	 */
  	if (recurse)
! 	{
! 		/* Recurse to child tables that are foreign, too */
! 		ATSimpleRecursion(wqueue, rel, cmd, recurse, true, lockmode);
! 	}
  	else if (!recursing &&
  			 find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
  		ereport(ERROR,
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 61,66 **** int			vacuum_multixact_freeze_table_age;
--- 61,67 ----
  
  /* A few variables that don't seem worth passing around as parameters */
  static MemoryContext vac_context = NULL;
+ static VacuumMode vac_mode;
  static BufferAccessStrategy vac_strategy;
  
  
***************
*** 146,151 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 147,166 ----
  										ALLOCSET_DEFAULT_MAXSIZE);
  
  	/*
+ 	 * Identify vacuum mode.  If relid is not InvalidOid, the caller should be
+ 	 * an autovacuum worker.  See the above comments.
+ 	 */
+ 	if (relid != InvalidOid)
+ 		vac_mode = VAC_MODE_AUTOVACUUM;
+ 	else
+ 	{
+ 		if (!vacstmt->relation)
+ 			vac_mode = VAC_MODE_ALL;
+ 		else
+ 			vac_mode = VAC_MODE_SINGLE;
+ 	}
+ 
+ 	/*
  	 * If caller didn't give us a buffer strategy object, make one in the
  	 * cross-transaction memory context.
  	 */
***************
*** 248,254 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
  					PushActiveSnapshot(GetTransactionSnapshot());
  				}
  
! 				analyze_rel(relid, vacstmt, vac_strategy);
  
  				if (use_own_xacts)
  				{
--- 263,269 ----
  					PushActiveSnapshot(GetTransactionSnapshot());
  				}
  
! 				analyze_rel(relid, vacstmt, vac_mode, vac_strategy);
  
  				if (use_own_xacts)
  				{
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1337,1347 **** 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.
  		 */
  		childrte = copyObject(rte);
  		childrte->relid = childOID;
  		childrte->inh = false;
  		childrte->requiredPerms = 0;
  		parse->rtable = lappend(parse->rtable, childrte);
--- 1337,1348 ----
  		/*
  		 * 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,
! 		 * 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);
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
***************
*** 16,21 ****
--- 16,22 ----
  
  #include <math.h>
  
+ #include "foreign/fdwapi.h"
  #include "miscadmin.h"
  #include "nodes/nodeFuncs.h"
  #include "optimizer/clauses.h"
***************
*** 2062,2067 **** reparameterize_path(PlannerInfo *root, Path *path,
--- 2063,2093 ----
  		case T_SubqueryScan:
  			return create_subqueryscan_path(root, rel, path->pathkeys,
  											required_outer);
+ 		case T_ForeignScan:
+ 			{
+ 				ForeignPath *newpath = NULL;
+ 
+ 				/* Let the FDW reparameterize the path node if possible */
+ 				if (rel->fdwroutine->ReparameterizeForeignPath != NULL)
+ 				{
+ 					Index		scan_relid = rel->relid;
+ 					RangeTblEntry *rte;
+ 
+ 					/* it should be a base rel... */
+ 					Assert(scan_relid > 0);
+ 					Assert(rel->rtekind == RTE_RELATION);
+ 					rte = planner_rt_fetch(scan_relid, root);
+ 					Assert(rte->rtekind == RTE_RELATION);
+ 
+ 					newpath =
+ 						rel->fdwroutine->ReparameterizeForeignPath(root,
+ 																   rel,
+ 																   rte->relid,
+ 														  (ForeignPath *) path,
+ 															   required_outer);
+ 				}
+ 				return (Path *) newpath;
+ 			}
  		default:
  			break;
  	}
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4209,4240 **** AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_o
  CreateForeignTableStmt:
  		CREATE FOREIGN TABLE qualified_name
  			'(' OptTableElementList ')'
! 			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.if_not_exists = false;
  					/* FDW-specific data */
! 					n->servername = $9;
! 					n->options = $10;
  					$$ = (Node *) n;
  				}
  		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
  			'(' OptTableElementList ')'
! 			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.if_not_exists = true;
  					/* FDW-specific data */
! 					n->servername = $12;
! 					n->options = $13;
  					$$ = (Node *) n;
  				}
  		;
--- 4209,4240 ----
  CreateForeignTableStmt:
  		CREATE FOREIGN TABLE qualified_name
  			'(' OptTableElementList ')'
! 			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 = $8;
  					n->base.if_not_exists = false;
  					/* FDW-specific data */
! 					n->servername = $10;
! 					n->options = $11;
  					$$ = (Node *) n;
  				}
  		| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
  			'(' OptTableElementList ')'
! 			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 = $11;
  					n->base.if_not_exists = true;
  					/* FDW-specific data */
! 					n->servername = $13;
! 					n->options = $14;
  					$$ = (Node *) n;
  				}
  		;
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 515,526 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
  				break;
  
  			case CONSTR_CHECK:
- 				if (cxt->isforeign)
- 					ereport(ERROR,
- 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- 					errmsg("constraints are not supported on foreign tables"),
- 							 parser_errposition(cxt->pstate,
- 												constraint->location)));
  				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
  				break;
  
--- 515,520 ----
***************
*** 529,535 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
  				if (cxt->isforeign)
  					ereport(ERROR,
  							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					errmsg("constraints are not supported on foreign tables"),
  							 parser_errposition(cxt->pstate,
  												constraint->location)));
  				if (constraint->keys == NIL)
--- 523,529 ----
  				if (cxt->isforeign)
  					ereport(ERROR,
  							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					errmsg("primary key or unique constraints are not supported on foreign tables"),
  							 parser_errposition(cxt->pstate,
  												constraint->location)));
  				if (constraint->keys == NIL)
***************
*** 546,552 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
  				if (cxt->isforeign)
  					ereport(ERROR,
  							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					errmsg("constraints are not supported on foreign tables"),
  							 parser_errposition(cxt->pstate,
  												constraint->location)));
  
--- 540,546 ----
  				if (cxt->isforeign)
  					ereport(ERROR,
  							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					errmsg("foreign key constraints are not supported on foreign tables"),
  							 parser_errposition(cxt->pstate,
  												constraint->location)));
  
***************
*** 605,614 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
  static void
  transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
  {
! 	if (cxt->isforeign)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("constraints are not supported on foreign tables"),
  				 parser_errposition(cxt->pstate,
  									constraint->location)));
  
--- 599,612 ----
  static void
  transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
  {
! 	if (cxt->isforeign &&
! 		(constraint->contype == CONSTR_PRIMARY ||
! 		 constraint->contype == CONSTR_UNIQUE ||
! 		 constraint->contype == CONSTR_EXCLUSION ||
! 		 constraint->contype == CONSTR_FOREIGN))
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("primary key, unique, exclusion, or foreign key constraints are not supported on foreign tables"),
  				 parser_errposition(cxt->pstate,
  									constraint->location)));
  
*** a/src/include/commands/vacuum.h
--- b/src/include/commands/vacuum.h
***************
*** 140,145 **** extern int	vacuum_multixact_freeze_min_age;
--- 140,154 ----
  extern int	vacuum_multixact_freeze_table_age;
  
  
+ /* Possible modes for vacuum() */
+ typedef enum
+ {
+ 	VAC_MODE_ALL,				/* Vacuum/analyze all relations */
+ 	VAC_MODE_SINGLE,			/* Vacuum/analyze a specific relation */
+ 	VAC_MODE_AUTOVACUUM			/* Autovacuum worker */
+ } VacuumMode;
+ 
+ 
  /* in commands/vacuum.c */
  extern void vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
  	   BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel);
***************
*** 174,180 **** extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
  				BufferAccessStrategy bstrategy);
  
  /* in commands/analyze.c */
! extern void analyze_rel(Oid relid, VacuumStmt *vacstmt,
  			BufferAccessStrategy bstrategy);
  extern bool std_typanalyze(VacAttrStats *stats);
  extern double anl_random_fract(void);
--- 183,191 ----
  				BufferAccessStrategy bstrategy);
  
  /* in commands/analyze.c */
! extern void analyze_rel(Oid relid,
! 			VacuumStmt *vacstmt,
! 			VacuumMode vacmode,
  			BufferAccessStrategy bstrategy);
  extern bool std_typanalyze(VacAttrStats *stats);
  extern double anl_random_fract(void);
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
***************
*** 31,36 **** typedef void (*GetForeignPaths_function) (PlannerInfo *root,
--- 31,42 ----
  													  RelOptInfo *baserel,
  													  Oid foreigntableid);
  
+ typedef ForeignPath *(*ReparameterizeForeignPath_function) (PlannerInfo *root,
+ 														 RelOptInfo *baserel,
+ 														  Oid foreigntableid,
+ 															ForeignPath *path,
+ 													  Relids required_outer);
+ 
  typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
  														 RelOptInfo *baserel,
  														  Oid foreigntableid,
***************
*** 117,122 **** typedef struct FdwRoutine
--- 123,129 ----
  	/* Functions for scanning foreign tables */
  	GetForeignRelSize_function GetForeignRelSize;
  	GetForeignPaths_function GetForeignPaths;
+ 	ReparameterizeForeignPath_function ReparameterizeForeignPath;
  	GetForeignPlan_function GetForeignPlan;
  	BeginForeignScan_function BeginForeignScan;
  	IterateForeignScan_function IterateForeignScan;
*** a/src/test/regress/expected/foreign_data.out
--- b/src/test/regress/expected/foreign_data.out
***************
*** 750,765 **** CREATE TABLE use_ft1_column_type (x ft1);
  ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer;	-- ERROR
  ERROR:  cannot alter foreign table "ft1" because column "use_ft1_column_type.x" uses its row type
  DROP TABLE use_ft1_column_type;
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0); -- ERROR
! ERROR:  constraints are not supported on foreign tables
! LINE 1: ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c...
!                                     ^
  ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const;               -- ERROR
! ERROR:  "ft1" is not a table
  ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
! ERROR:  "ft1" is not a table
! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check;
! ERROR:  "ft1" is not a table
  ALTER FOREIGN TABLE ft1 SET WITH OIDS;                          -- ERROR
  ERROR:  "ft1" is not a table
  ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
--- 750,761 ----
  ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET DATA TYPE integer;	-- ERROR
  ERROR:  cannot alter foreign table "ft1" because column "use_ft1_column_type.x" uses its row type
  DROP TABLE use_ft1_column_type;
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c9_check CHECK (c9 < 0);
  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 DROP CONSTRAINT ft1_c9_check;
  ALTER FOREIGN TABLE ft1 SET WITH OIDS;                          -- ERROR
  ERROR:  "ft1" is not a table
  ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
*** a/src/test/regress/sql/foreign_data.sql
--- b/src/test/regress/sql/foreign_data.sql
***************
*** 314,323 **** ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STATISTICS -1;
  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 CONSTRAINT ft1_c9_check CHECK (c9 < 0); -- ERROR
  ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const;               -- ERROR
  ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c1_check;
  ALTER FOREIGN TABLE ft1 SET WITH OIDS;                          -- ERROR
  ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
  ALTER FOREIGN TABLE ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@');
--- 314,323 ----
  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 CONSTRAINT ft1_c9_check CHECK (c9 < 0);
  ALTER FOREIGN TABLE ft1 DROP CONSTRAINT no_const;               -- ERROR
  ALTER FOREIGN TABLE ft1 DROP CONSTRAINT IF EXISTS no_const;
! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c9_check;
  ALTER FOREIGN TABLE ft1 SET WITH OIDS;                          -- ERROR
  ALTER FOREIGN TABLE ft1 OWNER TO regress_test_role;
  ALTER FOREIGN TABLE ft1 OPTIONS (DROP delimiter, SET quote '~', ADD escape '@');


view thread (179+ messages)  latest in thread

Message-ID: <53341F3C.2050609@lab.ntt.co.jp>
Permalink:  ../53341F3C.2050609@lab.ntt.co.jp/
Also on:    postgresql.org/message-id/53341F3C.2050609@lab.ntt.co.jp

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: fujita.etsuro@lab.ntt.co.jp, horiguchi.kyotaro@lab.ntt.co.jp, shigeru.hanada@gmail.com
  Subject: Re: inherit support for foreign tables
  In-Reply-To: <53341F3C.2050609@lab.ntt.co.jp>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox