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, 13 Mar 2014 22:00:02 +0900
Message-ID: <5321ABD2.6000104@lab.ntt.co.jp> (raw)
In-Reply-To: <20140310.172911.79971075.horiguchi.kyotaro@lab.ntt.co.jp>
References: <52FDF74A.5070203@lab.ntt.co.jp>
	<530C7464.6020006@lab.ntt.co.jp>
	<20140226.124857.17636723.horiguchi.kyotaro@lab.ntt.co.jp>
	<20140310.172911.79971075.horiguchi.kyotaro@lab.ntt.co.jp>
List-Unsubscribe: <mailto:majordomo@postgresql.org?body=unsub%20pgsql-hackers>

Hi Horiguchi-san,

Thank you for working this patch!

(2014/03/10 17:29), Kyotaro HORIGUCHI wrote:
> Hello. As a minimal implementation, I made an attempt that emit
> NOTICE message when alter table affects foreign tables. It looks
> like following,
>
> | =# alter table passwd add column added int, add column added2 int;
> | NOTICE:  This command affects foreign relation "cf1"
> | NOTICE:  This command affects foreign relation "cf1"
> | ALTER TABLE
> | =# select * from passwd;
> | ERROR:  missing data for column "added"
> | CONTEXT:  COPY cf1, line 1: "root:x:0:0:root:/root:/bin/bash"
> | =#
>
> This seems far better than silently performing the command,
> except for the duplicated message:( New bitmap might required to
> avoid the duplication..

As I said before, I think it would be better to show this kind of 
information on each of the affected tables whether or not the affected 
one is foreign.  I also think it would be better to show it only when 
the user has specified an option to do so, similar to a VERBOSE option 
of other commands.  ISTM this should be implemented as a separate patch.

> I made the changes above and below as an attempt in the attached
> patch foreign_inherit-v4.patch
>
>> I think the problem is foreign childs in inheritance tables
>> prevents all menber in the inheritance relation from using
>> parameterized paths, correct?

Yes, I think so, too.

>> Hmm. I tried minimal implementation to do that. This omits cost
>> recalculation but seems to work as expected. This seems enough if
>> cost recalc is trivial here.

I think we should redo the cost/size estimate, because for example, 
greater parameterization leads to a smaller rowcount estimate, if I 
understand correctly.  In addition, I think this reparameterization 
should be done by the FDW itself, becasuse the FDW has more knowledge 
about it than the PG core.  So, I think we should introduce a new FDW 
routine for that, say ReparameterizeForeignPath(), as proposed in [1]. 
Attached is an updated version of the patch.  Due to the above reason, I 
removed from the patch the message displaying function you added.

Sorry for the delay.

[1] http://www.postgresql.org/message-id/530C7464.6020006@lab.ntt.co.jp

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,126 ----
  static void fileGetForeignPaths(PlannerInfo *root,
  					RelOptInfo *baserel,
  					Oid foreigntableid);
+ static ForeignPath *fileReparameterizeForeignPath(PlannerInfo *root,
+ 												  RelOptInfo *baserel,
+ 												  Path *path,
+ 												  Relids required_outer);
  static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
  				   RelOptInfo *baserel,
  				   Oid foreigntableid,
***************
*** 145,150 **** static bool check_selective_binary_conversion(RelOptInfo *baserel,
--- 149,155 ----
  static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
  			  FileFdwPlanState *fdw_private);
  static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+ 			   ParamPathInfo *param_info,
  			   FileFdwPlanState *fdw_private,
  			   Cost *startup_cost, Cost *total_cost);
  static int file_acquire_sample_rows(Relation onerel, int elevel,
***************
*** 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,
! 				   NULL, fdw_private,
  				   &startup_cost, &total_cost);
  
  	/*
***************
*** 542,547 **** fileGetForeignPaths(PlannerInfo *root,
--- 549,595 ----
  }
  
  /*
+  * fileReparameterizeForeignPath
+  *		Attempt to modify a given path to have greater parameterization
+  */
+ static ForeignPath *
+ fileReparameterizeForeignPath(PlannerInfo *root,
+ 							  RelOptInfo *baserel,
+ 							  Path *path,
+ 							  Relids required_outer)
+ {
+ 	ForeignPath *newpath = makeNode(ForeignPath);
+ 	ParamPathInfo *param_info;
+ 	FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private;
+ 	Cost		startup_cost;
+ 	Cost		total_cost;
+ 
+ 	/*
+ 	 * To avoid re-checking whether to selectively perform binary conversion,
+ 	 * we create a ForeignPath node from by flat-copying the given path node,
+ 	 * rewriting the param_info and row estimate, and redoing the cost estimate.
+ 	 */
+ 	memcpy(newpath, (ForeignPath *) path, sizeof(ForeignPath));
+ 
+ 	/* Get the ParamPathInfo */
+ 	param_info = get_baserel_parampathinfo(root, baserel, required_outer);
+ 	Assert(param_info);
+ 
+ 	newpath->path.param_info = param_info;
+ 	newpath->path.rows = param_info->ppi_rows;
+ 
+ 	/* Redo the cost estimate */		
+ 	estimate_costs(root, baserel,
+ 				   param_info, fdw_private,
+ 				   &startup_cost, &total_cost);
+ 
+ 	newpath->path.startup_cost = startup_cost;
+ 	newpath->path.total_cost = total_cost;
+ 
+ 	return newpath;
+ }
+ 
+ /*
   * fileGetForeignPlan
   *		Create a ForeignScan plan node for scanning the foreign table
   */
***************
*** 970,981 **** estimate_size(PlannerInfo *root, RelOptInfo *baserel,
--- 1018,1031 ----
   */
  static void
  estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+ 			   ParamPathInfo *param_info,
  			   FileFdwPlanState *fdw_private,
  			   Cost *startup_cost, Cost *total_cost)
  {
  	BlockNumber pages = fdw_private->pages;
  	double		ntuples = fdw_private->ntuples;
  	Cost		run_cost = 0;
+ 	QualCost	qpqual_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;
  }
--- 1036,1045 ----
  	 */
  	run_cost += seq_page_cost * pages;
  
! 	get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
! 
! 	*startup_cost = qpqual_cost.startup;
! 	cpu_per_tuple = cpu_tuple_cost * 10 + qpqual_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,250 ----
  static void postgresGetForeignPaths(PlannerInfo *root,
  						RelOptInfo *baserel,
  						Oid foreigntableid);
+ static ForeignPath *postgresReparameterizeForeignPath(PlannerInfo *root,
+ 													  RelOptInfo *baserel,
+ 													  Path *path,
+ 													  Relids required_outer);
  static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
  					   RelOptInfo *baserel,
  					   Oid foreigntableid,
***************
*** 342,347 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 346,352 ----
  	/* 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,
--- 734,780 ----
  }
  
  /*
+  * postgresReparameterizeForeignPath
+  *		Attempt to modify a given path to have greater parameterization
+  */
+ static ForeignPath *
+ postgresReparameterizeForeignPath(PlannerInfo *root,
+ 								  RelOptInfo *baserel,
+ 								  Path *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 estimate */
+ 	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 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;
--- 1821,1828 ----
  	}
  	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;
--- 1835,1863 ----
  		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/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 258,263 **** CREATE TABLE products (
--- 258,274 ----
     even if the value came from the default value definition.
    </para>
  
+   <note>
+    <para>
+     Note that constraints can be defined on foreign tables too, but such
+     constraints are not enforced on insert or update.  Those constraints are
+     "assertive", and work only to tell planner that some kind of optimization
+     such as constraint exclusion can be considerd.  This seems useless, but
+     allows us to use foriegn table as child table (see
+     <xref linkend="ddl-inherit">) to off-load to multiple servers.
+    </para>
+   </note>
+ 
    <sect2 id="ddl-constraints-check-constraints">
     <title>Check Constraints</title>
  
***************
*** 2017,2024 **** CREATE TABLE capitals (
    </para>
  
    <para>
!    In <productname>PostgreSQL</productname>, a table can inherit from
!    zero or more other tables, and a query can reference either all
     rows of a table or all rows of a table plus all of its descendant tables.
     The latter behavior is the default.
     For example, the following query finds the names of all cities,
--- 2028,2035 ----
    </para>
  
    <para>
!    In <productname>PostgreSQL</productname>, a table or foreign table can
!    inherit from zero or more other tables, and a query can reference either all
     rows of a table or all rows of a table plus all of its descendant tables.
     The latter behavior is the default.
     For example, the following query finds the names of all cities,
*** 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,49 ----
      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>'] [, ... ])
+     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>
***************
*** 178,183 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 180,205 ----
     </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 a plain 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>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
      <listitem>
       <para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 328,343 ----
         </para>
        </listitem>
       </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 a plain table.
+        </para>
+       </listitem>
+      </varlistentry>
      </variablelist>
   </refsect1>
  
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 22,27 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 22,28 ----
      <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> [ ... ] ]
      [, ... ]
  ] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
    SERVER <replaceable class="parameter">server_name</replaceable>
  [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
  
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 160,177 ----
     </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 a plain 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>
*** a/src/backend/catalog/pg_inherits.c
--- b/src/backend/catalog/pg_inherits.c
***************
*** 256,261 **** has_subclass(Oid relationId)
--- 256,303 ----
  
  
  /*
+  * Check wether the inheritance tree contains foreign table(s).
+  */
+ bool
+ contains_foreign(Oid parentrelId, LOCKMODE lockmode)
+ {
+ 	bool		result = false;
+ 	List	   *tableOIDs;
+ 	ListCell   *lc;
+ 
+ 	/* Find all members of the inheritance tree */
+ 	tableOIDs = find_all_inheritors(parentrelId, lockmode, NULL);
+ 
+ 	/* There are no children */
+ 	if (list_length(tableOIDs) < 2)
+ 		return result;
+ 
+ 	foreach(lc, tableOIDs)
+ 	{
+ 		Oid			childOID = lfirst_oid(lc);
+ 		Relation	childrel;
+ 
+ 		/* Parent should not be foreign */
+ 		if (childOID == parentrelId)
+ 			continue;
+ 
+ 		/* We already got the needed lock */
+ 		childrel = heap_open(childOID, NoLock);
+ 
+ 		if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ 		{
+ 			/* Found it */
+ 			result = true;
+ 		}
+ 
+ 		heap_close(childrel, lockmode);
+ 	}
+ 
+ 	return result;
+ }
+ 
+ 
+ /*
   * Given two type OIDs, determine whether the first is a complex type
   * (class type) that inherits from the second.
   */
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 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;
--- 115,122 ----
   *	analyze_rel() -- analyze one relation
   */
  void
! analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode,
! 			BufferAccessStrategy bstrategy)
  {
  	Relation	onerel;
  	int			elevel;
***************
*** 270,276 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
  	/*
  	 * If there are child tables, do recursive ANALYZE.
  	 */
! 	if (onerel->rd_rel->relhassubclass)
  		do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
  
  	/*
--- 271,279 ----
  	/*
  	 * If there are child tables, do recursive ANALYZE.
  	 */
! 	if (onerel->rd_rel->relhassubclass &&
! 		(vacmode == VAC_MODE_SINGLE ||
! 		 !contains_foreign(RelationGetRelid(onerel), AccessShareLock)))
  		do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
  
  	/*
***************
*** 1452,1463 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1455,1469 ----
  {
  	List	   *tableOIDs;
  	Relation   *rels;
+ 	AcquireSampleRowsFunc *acquirefunc;
  	double	   *relblocks;
  	double		totalblocks;
+ 	Relation	saved_rel;
  	int			numrows,
  				nrels,
  				i;
  	ListCell   *lc;
+ 	bool		isAnalyzable = true;
  
  	/*
  	 * Find all members of inheritance set.  We only need AccessShareLock on
***************
*** 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,1518 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
  		}
  
  		rels[nrels] = childrel;
! 		relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
  		totalblocks += relblocks[nrels];
  		nrels++;
  	}
  
  	/*
  	 * Now sample rows from each relation, proportionally to its fraction of
  	 * the total block count.  (This might be less than desirable if the child
  	 * rels have radically different free-space percentages, but it's not
--- 1515,1573 ----
  		}
  
  		rels[nrels] = childrel;
! 
! 		if (childrel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
! 		{
! 			acquirefunc[nrels] = acquire_sample_rows;
! 			relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
! 		}
! 		else
! 		{
! 			/*
! 			 * For a foreign table, call the FDW's hook function to see whether
! 			 * it supports analysis.
! 			 */
! 			FdwRoutine *fdwroutine;
! 			BlockNumber relpages = 0;
! 			bool		ok = false;
! 
! 			fdwroutine = GetFdwRoutineForRelation(childrel, false);
! 			if (fdwroutine->AnalyzeForeignTable != NULL)
! 				ok = fdwroutine->AnalyzeForeignTable(childrel,
! 													 &acquirefunc[nrels],
! 													 &relpages);
! 			if (!ok)
! 			{
! 				isAnalyzable = false;
! 				break;
! 			}
! 
! 			relblocks[nrels] = (double) relpages;
! 		}
! 
  		totalblocks += relblocks[nrels];
  		nrels++;
  	}
  
  	/*
+ 	 * If there is at least one foreign table that cannot be analyzed, give up.
+ 	 */
+ 	if (!isAnalyzable)
+ 	{
+ 		ereport(WARNING,
+ 				(errmsg("skipping \"%s\" inheritance tree --- cannot analyze foreign table \"%s\"",
+ 						RelationGetRelationName(onerel),
+ 						RelationGetRelationName(rels[nrels]))));
+ 		for (i = 0; i < nrels; i++)
+ 		{
+ 			Relation	childrel = rels[i];
+ 
+ 			heap_close(childrel, NoLock);
+ 		}
+ 		return 0;
+ 	}
+ 
+ 	/*
  	 * Now sample rows from each relation, proportionally to its fraction of
  	 * the total block count.  (This might be less than desirable if the child
  	 * rels have radically different free-space percentages, but it's not
***************
*** 1525,1530 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1580,1586 ----
  	{
  		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 &&
--- 1596,1607 ----
  							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);
! 			/* Recurses 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);
! 			/* Recurses 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);
! 			/* Recurses 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 */
! 			/* Recurses 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,3161 **** 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;
  			break;
  		case AT_ValidateConstraint:		/* VALIDATE CONSTRAINT */
--- 3151,3169 ----
  			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 | ATT_FOREIGN_TABLE);
  			pass = AT_PASS_MISC;
  			break;
  		case AT_ValidateConstraint:		/* VALIDATE CONSTRAINT */
***************
*** 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);
  
***************
*** 4859,4865 **** ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
--- 4871,4889 ----
  	ATPrepAddColumn(wqueue, rel, recurse, false, cmd, lockmode);
  
  	if (recurse)
+ 	{
+ 		/*
+ 		 * Don't allow to add an OID column to inheritance tree that contains
+ 		 * foreign table(s)
+ 		 */
+ 	  if (contains_foreign(RelationGetRelid(rel), AccessShareLock))
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 					 errmsg("cannot add OID column to inheritance tree \"%s\" because it contains foreign table(s)",
+ 							RelationGetRelationName(rel))));
+ 
  		cmd->subtype = AT_AddOidsRecurse;
+ 	}
  }
  
  /*
***************
*** 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
--- 5364,5370 ----
  
  	/* 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
--- 5756,5769 ----
  
  	/* 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 on 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 */
--- 5774,5790 ----
  	 * 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);
  
--- 7264,7270 ----
  
  	/* 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,
--- 7599,7608 ----
  	 * alter would put them out of step.
  	 */
  	if (recurse)
! 	{
! 		/* Recurses 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
***************
*** 98,103 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 98,104 ----
  	   BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
  {
  	const char *stmttype;
+ 	VacuumMode	vacmode;
  	volatile bool in_outer_xact,
  				use_own_xacts;
  	List	   *relations;
***************
*** 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)
+ 		vacmode = VAC_MODE_AUTOVACUUM;
+ 	else
+ 	{
+ 		if (!vacstmt->relation)
+ 			vacmode = VAC_MODE_ALL;
+ 		else
+ 			vacmode = 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, vacmode, vac_strategy);
  
  				if (use_own_xacts)
  				{
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
***************
*** 132,140 **** static MergeScanSelCache *cached_scansel(PlannerInfo *root,
  static void cost_rescan(PlannerInfo *root, Path *path,
  			Cost *rescan_startup_cost, Cost *rescan_total_cost);
  static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
- static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
- 						  ParamPathInfo *param_info,
- 						  QualCost *qpqual_cost);
  static bool has_indexed_join_quals(NestPath *joinpath);
  static double approx_tuple_count(PlannerInfo *root, JoinPath *path,
  				   List *quals);
--- 132,137 ----
***************
*** 3157,3163 **** cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
   * some of the quals.  We assume baserestrictcost was previously set by
   * set_baserel_size_estimates().
   */
! static void
  get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
  						  ParamPathInfo *param_info,
  						  QualCost *qpqual_cost)
--- 3154,3160 ----
   * some of the quals.  We assume baserestrictcost was previously set by
   * set_baserel_size_estimates().
   */
! void
  get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
  						  ParamPathInfo *param_info,
  						  QualCost *qpqual_cost)
*** 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,2073 ----
  		case T_SubqueryScan:
  			return create_subqueryscan_path(root, rel, path->pathkeys,
  											required_outer);
+ 		case T_ForeignScan:
+ 			return (Path *) rel->fdwroutine->ReparameterizeForeignPath(root,
+ 																	   rel,
+ 																	   path,
+ 															required_outer);
  		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/catalog/pg_inherits_fn.h
--- b/src/include/catalog/pg_inherits_fn.h
***************
*** 21,26 **** extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
--- 21,27 ----
  extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode,
  					List **parents);
  extern bool has_subclass(Oid relationId);
+ extern bool contains_foreign(Oid parentrelId, LOCKMODE lockmode);
  extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
  
  #endif   /* PG_INHERITS_FN_H */
*** 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,189 ----
  				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,41 ----
  													  RelOptInfo *baserel,
  													  Oid foreigntableid);
  
+ typedef ForeignPath *(*ReparameterizeForeignPath_function) (PlannerInfo *root,
+ 														 RelOptInfo *baserel,
+ 															Path *path,
+ 													  Relids required_outer);
+ 
  typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
  														 RelOptInfo *baserel,
  														  Oid foreigntableid,
***************
*** 117,122 **** typedef struct FdwRoutine
--- 122,128 ----
  	/* 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/include/optimizer/cost.h
--- b/src/include/optimizer/cost.h
***************
*** 143,148 **** extern void final_cost_hashjoin(PlannerInfo *root, HashPath *path,
--- 143,151 ----
  extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan);
  extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root);
  extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root);
+ extern void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
+ 						  ParamPathInfo *param_info,
+ 						  QualCost *qpqual_cost);
  extern void compute_semi_anti_join_factors(PlannerInfo *root,
  							   RelOptInfo *outerrel,
  							   RelOptInfo *innerrel,
*** 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-v6.patch (51.5K, ../5321ABD2.6000104@lab.ntt.co.jp/2-foreign_inherit-v6.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,126 ----
  static void fileGetForeignPaths(PlannerInfo *root,
  					RelOptInfo *baserel,
  					Oid foreigntableid);
+ static ForeignPath *fileReparameterizeForeignPath(PlannerInfo *root,
+ 												  RelOptInfo *baserel,
+ 												  Path *path,
+ 												  Relids required_outer);
  static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
  				   RelOptInfo *baserel,
  				   Oid foreigntableid,
***************
*** 145,150 **** static bool check_selective_binary_conversion(RelOptInfo *baserel,
--- 149,155 ----
  static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
  			  FileFdwPlanState *fdw_private);
  static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+ 			   ParamPathInfo *param_info,
  			   FileFdwPlanState *fdw_private,
  			   Cost *startup_cost, Cost *total_cost);
  static int file_acquire_sample_rows(Relation onerel, int elevel,
***************
*** 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,
! 				   NULL, fdw_private,
  				   &startup_cost, &total_cost);
  
  	/*
***************
*** 542,547 **** fileGetForeignPaths(PlannerInfo *root,
--- 549,595 ----
  }
  
  /*
+  * fileReparameterizeForeignPath
+  *		Attempt to modify a given path to have greater parameterization
+  */
+ static ForeignPath *
+ fileReparameterizeForeignPath(PlannerInfo *root,
+ 							  RelOptInfo *baserel,
+ 							  Path *path,
+ 							  Relids required_outer)
+ {
+ 	ForeignPath *newpath = makeNode(ForeignPath);
+ 	ParamPathInfo *param_info;
+ 	FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private;
+ 	Cost		startup_cost;
+ 	Cost		total_cost;
+ 
+ 	/*
+ 	 * To avoid re-checking whether to selectively perform binary conversion,
+ 	 * we create a ForeignPath node from by flat-copying the given path node,
+ 	 * rewriting the param_info and row estimate, and redoing the cost estimate.
+ 	 */
+ 	memcpy(newpath, (ForeignPath *) path, sizeof(ForeignPath));
+ 
+ 	/* Get the ParamPathInfo */
+ 	param_info = get_baserel_parampathinfo(root, baserel, required_outer);
+ 	Assert(param_info);
+ 
+ 	newpath->path.param_info = param_info;
+ 	newpath->path.rows = param_info->ppi_rows;
+ 
+ 	/* Redo the cost estimate */		
+ 	estimate_costs(root, baserel,
+ 				   param_info, fdw_private,
+ 				   &startup_cost, &total_cost);
+ 
+ 	newpath->path.startup_cost = startup_cost;
+ 	newpath->path.total_cost = total_cost;
+ 
+ 	return newpath;
+ }
+ 
+ /*
   * fileGetForeignPlan
   *		Create a ForeignScan plan node for scanning the foreign table
   */
***************
*** 970,981 **** estimate_size(PlannerInfo *root, RelOptInfo *baserel,
--- 1018,1031 ----
   */
  static void
  estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+ 			   ParamPathInfo *param_info,
  			   FileFdwPlanState *fdw_private,
  			   Cost *startup_cost, Cost *total_cost)
  {
  	BlockNumber pages = fdw_private->pages;
  	double		ntuples = fdw_private->ntuples;
  	Cost		run_cost = 0;
+ 	QualCost	qpqual_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;
  }
--- 1036,1045 ----
  	 */
  	run_cost += seq_page_cost * pages;
  
! 	get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
! 
! 	*startup_cost = qpqual_cost.startup;
! 	cpu_per_tuple = cpu_tuple_cost * 10 + qpqual_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,250 ----
  static void postgresGetForeignPaths(PlannerInfo *root,
  						RelOptInfo *baserel,
  						Oid foreigntableid);
+ static ForeignPath *postgresReparameterizeForeignPath(PlannerInfo *root,
+ 													  RelOptInfo *baserel,
+ 													  Path *path,
+ 													  Relids required_outer);
  static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
  					   RelOptInfo *baserel,
  					   Oid foreigntableid,
***************
*** 342,347 **** postgres_fdw_handler(PG_FUNCTION_ARGS)
--- 346,352 ----
  	/* 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,
--- 734,780 ----
  }
  
  /*
+  * postgresReparameterizeForeignPath
+  *		Attempt to modify a given path to have greater parameterization
+  */
+ static ForeignPath *
+ postgresReparameterizeForeignPath(PlannerInfo *root,
+ 								  RelOptInfo *baserel,
+ 								  Path *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 estimate */
+ 	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 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;
--- 1821,1828 ----
  	}
  	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;
--- 1835,1863 ----
  		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/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 258,263 **** CREATE TABLE products (
--- 258,274 ----
     even if the value came from the default value definition.
    </para>
  
+   <note>
+    <para>
+     Note that constraints can be defined on foreign tables too, but such
+     constraints are not enforced on insert or update.  Those constraints are
+     "assertive", and work only to tell planner that some kind of optimization
+     such as constraint exclusion can be considerd.  This seems useless, but
+     allows us to use foriegn table as child table (see
+     <xref linkend="ddl-inherit">) to off-load to multiple servers.
+    </para>
+   </note>
+ 
    <sect2 id="ddl-constraints-check-constraints">
     <title>Check Constraints</title>
  
***************
*** 2017,2024 **** CREATE TABLE capitals (
    </para>
  
    <para>
!    In <productname>PostgreSQL</productname>, a table can inherit from
!    zero or more other tables, and a query can reference either all
     rows of a table or all rows of a table plus all of its descendant tables.
     The latter behavior is the default.
     For example, the following query finds the names of all cities,
--- 2028,2035 ----
    </para>
  
    <para>
!    In <productname>PostgreSQL</productname>, a table or foreign table can
!    inherit from zero or more other tables, and a query can reference either all
     rows of a table or all rows of a table plus all of its descendant tables.
     The latter behavior is the default.
     For example, the following query finds the names of all cities,
*** 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,49 ----
      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>'] [, ... ])
+     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>
***************
*** 178,183 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 180,205 ----
     </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 a plain 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>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
      <listitem>
       <para>
***************
*** 306,311 **** ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceab
--- 328,343 ----
         </para>
        </listitem>
       </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 a plain table.
+        </para>
+       </listitem>
+      </varlistentry>
      </variablelist>
   </refsect1>
  
*** a/doc/src/sgml/ref/create_foreign_table.sgml
--- b/doc/src/sgml/ref/create_foreign_table.sgml
***************
*** 22,27 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 22,28 ----
      <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> [ ... ] ]
      [, ... ]
  ] )
+ [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
    SERVER <replaceable class="parameter">server_name</replaceable>
  [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
  
***************
*** 159,164 **** CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
--- 160,177 ----
     </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 a plain 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>
*** a/src/backend/catalog/pg_inherits.c
--- b/src/backend/catalog/pg_inherits.c
***************
*** 256,261 **** has_subclass(Oid relationId)
--- 256,303 ----
  
  
  /*
+  * Check wether the inheritance tree contains foreign table(s).
+  */
+ bool
+ contains_foreign(Oid parentrelId, LOCKMODE lockmode)
+ {
+ 	bool		result = false;
+ 	List	   *tableOIDs;
+ 	ListCell   *lc;
+ 
+ 	/* Find all members of the inheritance tree */
+ 	tableOIDs = find_all_inheritors(parentrelId, lockmode, NULL);
+ 
+ 	/* There are no children */
+ 	if (list_length(tableOIDs) < 2)
+ 		return result;
+ 
+ 	foreach(lc, tableOIDs)
+ 	{
+ 		Oid			childOID = lfirst_oid(lc);
+ 		Relation	childrel;
+ 
+ 		/* Parent should not be foreign */
+ 		if (childOID == parentrelId)
+ 			continue;
+ 
+ 		/* We already got the needed lock */
+ 		childrel = heap_open(childOID, NoLock);
+ 
+ 		if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ 		{
+ 			/* Found it */
+ 			result = true;
+ 		}
+ 
+ 		heap_close(childrel, lockmode);
+ 	}
+ 
+ 	return result;
+ }
+ 
+ 
+ /*
   * Given two type OIDs, determine whether the first is a complex type
   * (class type) that inherits from the second.
   */
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 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;
--- 115,122 ----
   *	analyze_rel() -- analyze one relation
   */
  void
! analyze_rel(Oid relid, VacuumStmt *vacstmt, VacuumMode vacmode,
! 			BufferAccessStrategy bstrategy)
  {
  	Relation	onerel;
  	int			elevel;
***************
*** 270,276 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
  	/*
  	 * If there are child tables, do recursive ANALYZE.
  	 */
! 	if (onerel->rd_rel->relhassubclass)
  		do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
  
  	/*
--- 271,279 ----
  	/*
  	 * If there are child tables, do recursive ANALYZE.
  	 */
! 	if (onerel->rd_rel->relhassubclass &&
! 		(vacmode == VAC_MODE_SINGLE ||
! 		 !contains_foreign(RelationGetRelid(onerel), AccessShareLock)))
  		do_analyze_rel(onerel, vacstmt, acquirefunc, relpages, true, elevel);
  
  	/*
***************
*** 1452,1463 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1455,1469 ----
  {
  	List	   *tableOIDs;
  	Relation   *rels;
+ 	AcquireSampleRowsFunc *acquirefunc;
  	double	   *relblocks;
  	double		totalblocks;
+ 	Relation	saved_rel;
  	int			numrows,
  				nrels,
  				i;
  	ListCell   *lc;
+ 	bool		isAnalyzable = true;
  
  	/*
  	 * Find all members of inheritance set.  We only need AccessShareLock on
***************
*** 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,1518 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
  		}
  
  		rels[nrels] = childrel;
! 		relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
  		totalblocks += relblocks[nrels];
  		nrels++;
  	}
  
  	/*
  	 * Now sample rows from each relation, proportionally to its fraction of
  	 * the total block count.  (This might be less than desirable if the child
  	 * rels have radically different free-space percentages, but it's not
--- 1515,1573 ----
  		}
  
  		rels[nrels] = childrel;
! 
! 		if (childrel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
! 		{
! 			acquirefunc[nrels] = acquire_sample_rows;
! 			relblocks[nrels] = (double) RelationGetNumberOfBlocks(childrel);
! 		}
! 		else
! 		{
! 			/*
! 			 * For a foreign table, call the FDW's hook function to see whether
! 			 * it supports analysis.
! 			 */
! 			FdwRoutine *fdwroutine;
! 			BlockNumber relpages = 0;
! 			bool		ok = false;
! 
! 			fdwroutine = GetFdwRoutineForRelation(childrel, false);
! 			if (fdwroutine->AnalyzeForeignTable != NULL)
! 				ok = fdwroutine->AnalyzeForeignTable(childrel,
! 													 &acquirefunc[nrels],
! 													 &relpages);
! 			if (!ok)
! 			{
! 				isAnalyzable = false;
! 				break;
! 			}
! 
! 			relblocks[nrels] = (double) relpages;
! 		}
! 
  		totalblocks += relblocks[nrels];
  		nrels++;
  	}
  
  	/*
+ 	 * If there is at least one foreign table that cannot be analyzed, give up.
+ 	 */
+ 	if (!isAnalyzable)
+ 	{
+ 		ereport(WARNING,
+ 				(errmsg("skipping \"%s\" inheritance tree --- cannot analyze foreign table \"%s\"",
+ 						RelationGetRelationName(onerel),
+ 						RelationGetRelationName(rels[nrels]))));
+ 		for (i = 0; i < nrels; i++)
+ 		{
+ 			Relation	childrel = rels[i];
+ 
+ 			heap_close(childrel, NoLock);
+ 		}
+ 		return 0;
+ 	}
+ 
+ 	/*
  	 * Now sample rows from each relation, proportionally to its fraction of
  	 * the total block count.  (This might be less than desirable if the child
  	 * rels have radically different free-space percentages, but it's not
***************
*** 1525,1530 **** acquire_inherited_sample_rows(Relation onerel, int elevel,
--- 1580,1586 ----
  	{
  		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 &&
--- 1596,1607 ----
  							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);
! 			/* Recurses 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);
! 			/* Recurses 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);
! 			/* Recurses 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 */
! 			/* Recurses 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,3161 **** 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;
  			break;
  		case AT_ValidateConstraint:		/* VALIDATE CONSTRAINT */
--- 3151,3169 ----
  			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 | ATT_FOREIGN_TABLE);
  			pass = AT_PASS_MISC;
  			break;
  		case AT_ValidateConstraint:		/* VALIDATE CONSTRAINT */
***************
*** 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);
  
***************
*** 4859,4865 **** ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
--- 4871,4889 ----
  	ATPrepAddColumn(wqueue, rel, recurse, false, cmd, lockmode);
  
  	if (recurse)
+ 	{
+ 		/*
+ 		 * Don't allow to add an OID column to inheritance tree that contains
+ 		 * foreign table(s)
+ 		 */
+ 	  if (contains_foreign(RelationGetRelid(rel), AccessShareLock))
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 					 errmsg("cannot add OID column to inheritance tree \"%s\" because it contains foreign table(s)",
+ 							RelationGetRelationName(rel))));
+ 
  		cmd->subtype = AT_AddOidsRecurse;
+ 	}
  }
  
  /*
***************
*** 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
--- 5364,5370 ----
  
  	/* 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
--- 5756,5769 ----
  
  	/* 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 on 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 */
--- 5774,5790 ----
  	 * 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);
  
--- 7264,7270 ----
  
  	/* 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,
--- 7599,7608 ----
  	 * alter would put them out of step.
  	 */
  	if (recurse)
! 	{
! 		/* Recurses 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
***************
*** 98,103 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast,
--- 98,104 ----
  	   BufferAccessStrategy bstrategy, bool for_wraparound, bool isTopLevel)
  {
  	const char *stmttype;
+ 	VacuumMode	vacmode;
  	volatile bool in_outer_xact,
  				use_own_xacts;
  	List	   *relations;
***************
*** 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)
+ 		vacmode = VAC_MODE_AUTOVACUUM;
+ 	else
+ 	{
+ 		if (!vacstmt->relation)
+ 			vacmode = VAC_MODE_ALL;
+ 		else
+ 			vacmode = 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, vacmode, vac_strategy);
  
  				if (use_own_xacts)
  				{
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
***************
*** 132,140 **** static MergeScanSelCache *cached_scansel(PlannerInfo *root,
  static void cost_rescan(PlannerInfo *root, Path *path,
  			Cost *rescan_startup_cost, Cost *rescan_total_cost);
  static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
- static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
- 						  ParamPathInfo *param_info,
- 						  QualCost *qpqual_cost);
  static bool has_indexed_join_quals(NestPath *joinpath);
  static double approx_tuple_count(PlannerInfo *root, JoinPath *path,
  				   List *quals);
--- 132,137 ----
***************
*** 3157,3163 **** cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
   * some of the quals.  We assume baserestrictcost was previously set by
   * set_baserel_size_estimates().
   */
! static void
  get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
  						  ParamPathInfo *param_info,
  						  QualCost *qpqual_cost)
--- 3154,3160 ----
   * some of the quals.  We assume baserestrictcost was previously set by
   * set_baserel_size_estimates().
   */
! void
  get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
  						  ParamPathInfo *param_info,
  						  QualCost *qpqual_cost)
*** 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,2073 ----
  		case T_SubqueryScan:
  			return create_subqueryscan_path(root, rel, path->pathkeys,
  											required_outer);
+ 		case T_ForeignScan:
+ 			return (Path *) rel->fdwroutine->ReparameterizeForeignPath(root,
+ 																	   rel,
+ 																	   path,
+ 															required_outer);
  		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/catalog/pg_inherits_fn.h
--- b/src/include/catalog/pg_inherits_fn.h
***************
*** 21,26 **** extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
--- 21,27 ----
  extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode,
  					List **parents);
  extern bool has_subclass(Oid relationId);
+ extern bool contains_foreign(Oid parentrelId, LOCKMODE lockmode);
  extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
  
  #endif   /* PG_INHERITS_FN_H */
*** 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,189 ----
  				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,41 ----
  													  RelOptInfo *baserel,
  													  Oid foreigntableid);
  
+ typedef ForeignPath *(*ReparameterizeForeignPath_function) (PlannerInfo *root,
+ 														 RelOptInfo *baserel,
+ 															Path *path,
+ 													  Relids required_outer);
+ 
  typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
  														 RelOptInfo *baserel,
  														  Oid foreigntableid,
***************
*** 117,122 **** typedef struct FdwRoutine
--- 122,128 ----
  	/* 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/include/optimizer/cost.h
--- b/src/include/optimizer/cost.h
***************
*** 143,148 **** extern void final_cost_hashjoin(PlannerInfo *root, HashPath *path,
--- 143,151 ----
  extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan);
  extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root);
  extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root);
+ extern void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
+ 						  ParamPathInfo *param_info,
+ 						  QualCost *qpqual_cost);
  extern void compute_semi_anti_join_factors(PlannerInfo *root,
  							   RelOptInfo *outerrel,
  							   RelOptInfo *innerrel,
*** 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: <5321ABD2.6000104@lab.ntt.co.jp>
Permalink:  ../5321ABD2.6000104@lab.ntt.co.jp/
Also on:    postgresql.org/message-id/5321ABD2.6000104@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: <5321ABD2.6000104@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