diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 29f203c6f10eeeed194670aa37956a6190e8e7a1..e8907709bd90a6342384dfb6f10b00e55018d65d 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -26,6 +26,8 @@
 #include "nodes/makefuncs.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/planmain.h"
+#include "optimizer/restrictinfo.h"
 #include "utils/rel.h"
 
 PG_MODULE_MAGIC;
@@ -48,7 +50,7 @@ struct FileFdwOption
  * Note: If you are adding new option for user mapping, you need to modify
  * fileGetOptions(), which currently doesn't bother to look at user mappings.
  */
-static struct FileFdwOption valid_options[] = {
+static const struct FileFdwOption valid_options[] = {
 	/* File options */
 	{"filename", ForeignTableRelationId},
 
@@ -71,6 +73,17 @@ static struct FileFdwOption valid_options[] = {
 	{NULL, InvalidOid}
 };
 
+/*
+ * FDW-specific information for RelOptInfo.fdw_private.
+ */
+typedef struct FileFdwPlanState
+{
+	char	   *filename;		/* file to read */
+	List	   *options;		/* merged COPY options, excluding filename */
+	BlockNumber pages;			/* estimate of file's physical size */
+	double		ntuples;		/* estimate of number of rows in file */
+} FileFdwPlanState;
+
 /*
  * FDW-specific information for ForeignScanState.fdw_state.
  */
@@ -93,9 +106,18 @@ PG_FUNCTION_INFO_V1(file_fdw_validator);
 /*
  * FDW callback routines
  */
-static void filePlanForeignScan(Oid foreigntableid,
-					PlannerInfo *root,
-					RelOptInfo *baserel);
+static void fileGetForeignRelSize(PlannerInfo *root,
+								  RelOptInfo *baserel,
+								  Oid foreigntableid);
+static void fileGetForeignPaths(PlannerInfo *root,
+								RelOptInfo *baserel,
+								Oid foreigntableid);
+static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
+									   RelOptInfo *baserel,
+									   Oid foreigntableid,
+									   ForeignPath *best_path,
+									   List *tlist,
+									   List *scan_clauses);
 static void fileExplainForeignScan(ForeignScanState *node, ExplainState *es);
 static void fileBeginForeignScan(ForeignScanState *node, int eflags);
 static TupleTableSlot *fileIterateForeignScan(ForeignScanState *node);
@@ -109,8 +131,10 @@ static bool is_valid_option(const char *option, Oid context);
 static void fileGetOptions(Oid foreigntableid,
 			   char **filename, List **other_options);
 static List *get_file_fdw_attribute_options(Oid relid);
+static void estimate_size(PlannerInfo *root, RelOptInfo *baserel,
+			  FileFdwPlanState *fdw_private);
 static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
-			   const char *filename,
+			   FileFdwPlanState *fdw_private,
 			   Cost *startup_cost, Cost *total_cost);
 
 
@@ -123,7 +147,9 @@ file_fdw_handler(PG_FUNCTION_ARGS)
 {
 	FdwRoutine *fdwroutine = makeNode(FdwRoutine);
 
-	fdwroutine->PlanForeignScan = filePlanForeignScan;
+	fdwroutine->GetForeignRelSize = fileGetForeignRelSize;
+	fdwroutine->GetForeignPaths = fileGetForeignPaths;
+	fdwroutine->GetForeignPlan = fileGetForeignPlan;
 	fdwroutine->ExplainForeignScan = fileExplainForeignScan;
 	fdwroutine->BeginForeignScan = fileBeginForeignScan;
 	fdwroutine->IterateForeignScan = fileIterateForeignScan;
@@ -177,7 +203,7 @@ file_fdw_validator(PG_FUNCTION_ARGS)
 
 		if (!is_valid_option(def->defname, catalog))
 		{
-			struct FileFdwOption *opt;
+			const struct FileFdwOption *opt;
 			StringInfoData buf;
 
 			/*
@@ -249,7 +275,7 @@ file_fdw_validator(PG_FUNCTION_ARGS)
 static bool
 is_valid_option(const char *option, Oid context)
 {
-	struct FileFdwOption *opt;
+	const struct FileFdwOption *opt;
 
 	for (opt = valid_options; opt->optname; opt++)
 	{
@@ -381,7 +407,31 @@ get_file_fdw_attribute_options(Oid relid)
 }
 
 /*
- * filePlanForeignScan
+ * fileGetForeignRelSize
+ *		Obtain relation size estimates for a foreign table
+ */
+static void
+fileGetForeignRelSize(PlannerInfo *root,
+					  RelOptInfo *baserel,
+					  Oid foreigntableid)
+{
+	FileFdwPlanState *fdw_private;
+
+	/*
+	 * Fetch options.  We only need filename at this point, but we might
+	 * as well get everything and not need to re-fetch it later in planning.
+	 */
+	fdw_private = (FileFdwPlanState *) palloc(sizeof(FileFdwPlanState));
+	fileGetOptions(foreigntableid,
+				   &fdw_private->filename, &fdw_private->options);
+	baserel->fdw_private = (void *) fdw_private;
+
+	/* Estimate relation size */
+	estimate_size(root, baserel, fdw_private);
+}
+
+/*
+ * fileGetForeignPaths
  *		Create possible access paths for a scan on the foreign table
  *
  *		Currently we don't support any push-down feature, so there is only one
@@ -389,20 +439,16 @@ get_file_fdw_attribute_options(Oid relid)
  *		the data file.
  */
 static void
-filePlanForeignScan(Oid foreigntableid,
-					PlannerInfo *root,
-					RelOptInfo *baserel)
+fileGetForeignPaths(PlannerInfo *root,
+					RelOptInfo *baserel,
+					Oid foreigntableid)
 {
-	char	   *filename;
-	List	   *options;
+	FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private;
 	Cost		startup_cost;
 	Cost		total_cost;
 
-	/* Fetch options --- we only need filename at this point */
-	fileGetOptions(foreigntableid, &filename, &options);
-
-	/* Estimate costs and update baserel->rows */
-	estimate_costs(root, baserel, filename,
+	/* Estimate costs */
+	estimate_costs(root, baserel, fdw_private,
 				   &startup_cost, &total_cost);
 
 	/* Create a ForeignPath node and add it as only possible path */
@@ -422,6 +468,37 @@ filePlanForeignScan(Oid foreigntableid,
 	 */
 }
 
+/*
+ * fileGetForeignPlan
+ *		Create a ForeignScan plan node for scanning the foreign table
+ */
+static ForeignScan *
+fileGetForeignPlan(PlannerInfo *root,
+				   RelOptInfo *baserel,
+				   Oid foreigntableid,
+				   ForeignPath *best_path,
+				   List *tlist,
+				   List *scan_clauses)
+{
+	Index		scan_relid = baserel->relid;
+
+	/*
+	 * We have no native ability to evaluate restriction clauses, so we just
+	 * put all the scan_clauses into the plan node's qual list for the
+	 * executor to check.  So all we have to do here is strip RestrictInfo
+	 * nodes from the clauses and ignore pseudoconstants (which will be
+	 * handled elsewhere).
+	 */
+	scan_clauses = extract_actual_clauses(scan_clauses, false);
+
+	/* Create the ForeignScan node */
+	return make_foreignscan(tlist,
+							scan_clauses,
+							scan_relid,
+							NIL, /* no expressions to evaluate */
+							NIL); /* no private state either */
+}
+
 /*
  * fileExplainForeignScan
  *		Produce extra output for EXPLAIN
@@ -568,38 +645,38 @@ fileReScanForeignScan(ForeignScanState *node)
 }
 
 /*
- * Estimate costs of scanning a foreign table.
+ * Estimate size of a foreign table.
  *
- * In addition to setting *startup_cost and *total_cost, this should
- * update baserel->rows.
+ * The main result is returned in baserel->rows.  We also set
+ * fdw_private->pages and fdw_private->ntuples for later use in the cost
+ * calculation.
  */
 static void
-estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
-			   const char *filename,
-			   Cost *startup_cost, Cost *total_cost)
+estimate_size(PlannerInfo *root, RelOptInfo *baserel,
+			  FileFdwPlanState *fdw_private)
 {
 	struct stat stat_buf;
 	BlockNumber pages;
 	int			tuple_width;
 	double		ntuples;
 	double		nrows;
-	Cost		run_cost = 0;
-	Cost		cpu_per_tuple;
 
 	/*
 	 * Get size of the file.  It might not be there at plan time, though, in
 	 * which case we have to use a default estimate.
 	 */
-	if (stat(filename, &stat_buf) < 0)
+	if (stat(fdw_private->filename, &stat_buf) < 0)
 		stat_buf.st_size = 10 * BLCKSZ;
 
 	/*
-	 * Convert size to pages for use in I/O cost estimate below.
+	 * Convert size to pages for use in I/O cost estimate later.
 	 */
 	pages = (stat_buf.st_size + (BLCKSZ - 1)) / BLCKSZ;
 	if (pages < 1)
 		pages = 1;
 
+	fdw_private->pages = pages;
+
 	/*
 	 * Estimate the number of tuples in the file.  We back into this estimate
 	 * using the planner's idea of the relation width; which is bogus if not
@@ -611,6 +688,8 @@ estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
 
 	ntuples = clamp_row_est((double) stat_buf.st_size / (double) tuple_width);
 
+	fdw_private->ntuples = ntuples;
+
 	/*
 	 * Now estimate the number of rows returned by the scan after applying the
 	 * baserestrictinfo quals.	This is pretty bogus too, since the planner
@@ -627,12 +706,28 @@ estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
 
 	/* Save the output-rows estimate for the planner */
 	baserel->rows = nrows;
+}
+
+/*
+ * Estimate costs of scanning a foreign table.
+ *
+ * Results are returned in *startup_cost and *total_cost.
+ */
+static void
+estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+			   FileFdwPlanState *fdw_private,
+			   Cost *startup_cost, Cost *total_cost)
+{
+	BlockNumber pages = fdw_private->pages;
+	double		ntuples = fdw_private->ntuples;
+	Cost		run_cost = 0;
+	Cost		cpu_per_tuple;
 
 	/*
-	 * Now estimate costs.	We estimate costs almost the same way as
-	 * cost_seqscan(), thus assuming that I/O costs are equivalent to a
-	 * regular table file of the same size.  However, we take per-tuple CPU
-	 * costs as 10x of a seqscan, to account for the cost of parsing records.
+	 * We estimate costs almost the same way as cost_seqscan(), thus assuming
+	 * that I/O costs are equivalent to a regular table file of the same size.
+	 * However, we take per-tuple CPU costs as 10x of a seqscan, to account
+	 * for the cost of parsing records.
 	 */
 	run_cost += seq_page_cost * pages;
 
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index dbfcbbc2b36dd49b0f0a8ffc9893da0b2a25a891..f7bf3d8a39571380ca6c28a972216684fa6a31bb 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -89,52 +89,92 @@
     <para>
 <programlisting>
 void
-PlanForeignScan (Oid foreigntableid,
-                 PlannerInfo *root,
-                 RelOptInfo *baserel);
+GetForeignRelSize (PlannerInfo *root,
+                   RelOptInfo *baserel,
+                   Oid foreigntableid);
 </programlisting>
 
-     Create possible access paths for a scan on a foreign table. This is
-     called when a query is planned.
+     Obtain relation size estimates for a foreign table.  This is called
+     at the beginning of planning for a query involving a foreign table.
+     <literal>root</> is the planner's global information about the query;
+     <literal>baserel</> is the planner's information about this table; and
      <literal>foreigntableid</> is the <structname>pg_class</> OID of the
-     foreign table.  <literal>root</> is the planner's global information
-     about the query, and <literal>baserel</> is the planner's information
-     about this table.
+     foreign table.  (<literal>foreigntableid</> could be obtained from the
+     planner data structures, but it's passed explicitly to save effort.)
     </para>
 
     <para>
-     The function must generate at least one access path (ForeignPath node)
-     for a scan on the foreign table and must call <function>add_path</> to
-     add the path to <literal>baserel-&gt;pathlist</>.  It's recommended to
-     use <function>create_foreignscan_path</> to build the ForeignPath node.
-     The function may generate multiple access paths, e.g., a path which has
-     valid <literal>pathkeys</> to represent a pre-sorted result.  Each access
-     path must contain cost estimates, and can contain any FDW-private
-     information that is needed to execute the foreign scan at a later time.
-     (Note that the private information must be represented in a form that
-     <function>copyObject</> knows how to copy.)
+     This function should update <literal>baserel-&gt;rows</> to be the
+     expected number of rows returned by the table scan, after accounting for
+     the filtering done by the restriction quals.  The initial value of
+     <literal>baserel-&gt;rows</> is just a constant default estimate, which
+     should be replaced if at all possible.  The function may also choose to
+     update <literal>baserel-&gt;width</> if it can compute a better estimate
+     of the average result row width.
     </para>
 
     <para>
-     The information in <literal>root</> and <literal>baserel</> can be used
-     to reduce the amount of information that has to be fetched from the
-     foreign table (and therefore reduce the cost estimate).
-     <literal>baserel-&gt;baserestrictinfo</> is particularly interesting, as
-     it contains restriction quals (<literal>WHERE</> clauses) that can be
-     used to filter the rows to be fetched.  (The FDW is not required to
-     enforce these quals, as the finished plan will recheck them anyway.)
-     <literal>baserel-&gt;reltargetlist</> can be used to determine which
-     columns need to be fetched.
+     See <xref linkend="fdw-planning"> for additional information.
+    </para>
+
+    <para>
+<programlisting>
+void
+GetForeignPaths (PlannerInfo *root,
+                 RelOptInfo *baserel,
+                 Oid foreigntableid);
+</programlisting>
+
+     Create possible access paths for a scan on a foreign table.
+     This is called during query planning.
+     The parameters are the same as for <function>GetForeignRelSize</>,
+     which has already been called.
+    </para>
+
+    <para>
+     This function must generate at least one access path
+     (<structname>ForeignPath</> node) for a scan on the foreign table and
+     must call <function>add_path</> to add each such path to
+     <literal>baserel-&gt;pathlist</>.  It's recommended to use
+     <function>create_foreignscan_path</> to build the
+     <structname>ForeignPath</> nodes.  The function can generate multiple
+     access paths, e.g., a path which has valid <literal>pathkeys</> to
+     represent a pre-sorted result.  Each access path must contain cost
+     estimates, and can contain any FDW-private information that is needed to
+     identify the specific scan method intended.
+    </para>
+
+    <para>
+     See <xref linkend="fdw-planning"> for additional information.
+    </para>
+
+    <para>
+<programlisting>
+ForeignScan *
+GetForeignPlan (PlannerInfo *root,
+                RelOptInfo *baserel,
+                Oid foreigntableid,
+                ForeignPath *best_path,
+                List *tlist,
+                List *scan_clauses);
+</programlisting>
+
+     Create a <structname>ForeignScan</> plan node from the selected foreign
+     access path.  This is called at the end of query planning.
+     The parameters are as for <function>GetForeignRelSize</>, plus
+     the selected <structname>ForeignPath</> (previously produced by
+     <function>GetForeignPaths</>), the target list to be emitted by the
+     plan node, and the restriction clauses to be enforced by the plan node.
     </para>
 
     <para>
-     In addition to returning cost estimates, the function should update
-     <literal>baserel-&gt;rows</> to be the expected number of rows returned
-     by the scan, after accounting for the filtering done by the restriction
-     quals.  The initial value of <literal>baserel-&gt;rows</> is just a
-     constant default estimate, which should be replaced if at all possible.
-     The function may also choose to update <literal>baserel-&gt;width</> if
-     it can compute a better estimate of the average result row width.
+     This function must create and return a <structname>ForeignScan</> plan
+     node; it's recommended to use <function>make_foreignscan</> to build the
+     <structname>ForeignScan</> node.
+    </para>
+
+    <para>
+     See <xref linkend="fdw-planning"> for additional information.
     </para>
 
     <para>
@@ -170,7 +210,7 @@ BeginForeignScan (ForeignScanState *node,
      the table to scan is accessible through the
      <structname>ForeignScanState</> node (in particular, from the underlying
      <structname>ForeignScan</> plan node, which contains any FDW-private
-     information provided by <function>PlanForeignScan</>).
+     information provided by <function>GetForeignPlan</>).
     </para>
 
     <para>
@@ -347,6 +387,126 @@ GetForeignServerByName(const char *name, bool missing_ok);
      return NULL if missing_ok is true, otherwise raise an error.
     </para>
 
+   </sect1>
+
+   <sect1 id="fdw-planning">
+    <title>Foreign Data Wrapper Query Planning</title>
+
+    <para>
+     The FDW callback functions <function>GetForeignRelSize</>,
+     <function>GetForeignPaths</>, and <function>GetForeignPlan</> must fit
+     into the workings of the <productname>PostgreSQL</> planner.  Here are
+     some notes about what they must do.
+    </para>
+
+    <para>
+     The information in <literal>root</> and <literal>baserel</> can be used
+     to reduce the amount of information that has to be fetched from the
+     foreign table (and therefore reduce the cost).
+     <literal>baserel-&gt;baserestrictinfo</> is particularly interesting, as
+     it contains restriction quals (<literal>WHERE</> clauses) that should be
+     used to filter the rows to be fetched.  (The FDW itself is not required
+     to enforce these quals, as the core executor can check them instead.)
+     <literal>baserel-&gt;reltargetlist</> can be used to determine which
+     columns need to be fetched; but note that it only lists columns that
+     have to be emitted by the <structname>ForeignScan</> plan node, not
+     columns that are used in qual evaluation but not output by the query.
+    </para>
+
+    <para>
+     Various private fields are available for the FDW planning functions to
+     keep information in.  Generally, whatever you store in FDW private fields
+     should be palloc'd, so that it will be reclaimed at the end of planning.
+    </para>
+
+    <para>
+     <literal>baserel-&gt;fdw_private</> is a <type>void</> pointer that is
+     available for FDW planning functions to store information relevant to
+     the particular foreign table.  The core planner does not touch it except
+     to initialize it to NULL when the <literal>baserel</> node is created.
+     It is useful for passing information forward from
+     <function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
+     <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
+     avoiding recalculation.
+    </para>
+
+    <para>
+     <function>GetForeignPaths</> can identify the meaning of different
+     access paths by storing private information in the
+     <structfield>fdw_private</> field of <structname>ForeignPath</> nodes.
+     <structfield>fdw_private</> is declared as a <type>List</> pointer, but
+     could actually contain anything since the core planner does not touch
+     it.  However, best practice is to use a representation that's dumpable
+     by <function>nodeToString</>, for use with debugging support available
+     in the backend.
+    </para>
+
+    <para>
+     <function>GetForeignPlan</> can examine the <structfield>fdw_private</>
+     field of the selected <structname>ForeignPath</> node, and can generate
+     <structfield>fdw_exprs</> and <structfield>fdw_private</> lists to be
+     placed in the <structname>ForeignScan</> plan node, where they will be
+     available at execution time.  Both of these lists must be
+     represented in a form that <function>copyObject</> knows how to copy.
+     The <structfield>fdw_private</> list has no other restrictions and is
+     not interpreted by the core backend in any way.  The
+     <structfield>fdw_exprs</> list, if not NIL, is expected to contain
+     expression trees that are intended to be executed at runtime.  These
+     trees will undergo post-processing by the planner to make them fully
+     executable.
+    </para>
+
+    <para>
+     In <function>GetForeignPlan</>, generally the passed-in targetlist can
+     be copied into the plan node as-is.  The passed scan_clauses list
+     contains the same clauses as <literal>baserel-&gt;baserestrictinfo</>,
+     but may be re-ordered for better execution efficiency.  In simple cases
+     the FDW can just strip <structname>RestrictInfo</> nodes from the
+     scan_clauses list (using <function>extract_actual_clauses</>) and put
+     all the clauses into the plan node's qual list, which means that all the
+     clauses will be checked by the executor at runtime.  More complex FDWs
+     may be able to check some of the clauses internally, in which case those
+     clauses can be removed from the plan node's qual list so that the
+     executor doesn't waste time rechecking them.
+    </para>
+
+    <para>
+     As an example, the FDW might identify some restriction clauses of the
+     form <replaceable>foreign_variable</> <literal>=</>
+     <replaceable>sub_expression</>, which it determines can be executed on
+     the remote server given the locally-evaluated value of the
+     <replaceable>sub_expression</>.  The actual identification of such a
+     clause should happen during <function>GetForeignPaths</>, since it would
+     affect the cost estimate for the path.  The path's
+     <structfield>fdw_private</> field would probably include a pointer to
+     the identified clause's <structname>RestrictInfo</> node.  Then
+     <function>GetForeignPlan</> would remove that clause from scan_clauses,
+     but add the <replaceable>sub_expression</> to <structfield>fdw_exprs</>
+     to ensure that it gets massaged into executable form.  It would probably
+     also put control information into the plan node's
+     <structfield>fdw_private</> field to tell the execution functions what
+     to do at runtime.  The query transmitted to the remote server would
+     involve something like <literal>WHERE <replaceable>foreign_variable</> =
+     $1</literal>, with the parameter value obtained at runtime from
+     evaluation of the <structfield>fdw_exprs</> expression tree.
+    </para>
+
+    <para>
+     The FDW should always construct at least one path that depends only on
+     the table's restriction clauses.  In join queries, it might also choose
+     to construct path(s) that depend on join clauses, for example
+     <replaceable>foreign_variable</> <literal>=</>
+     <replaceable>local_variable</>.  Such clauses will not be found in
+     <literal>baserel-&gt;baserestrictinfo</> but must be sought in the
+     relation's join lists.  A path using such a clause is called a
+     <quote>parameterized path</>.  It must show the other relation(s) as
+     <literal>required_outer</> and list the specific join clause(s) in
+     <literal>param_clauses</>.  In <function>GetForeignPlan</>, the
+     <replaceable>local_variable</> portion of the join clause would be added
+     to <structfield>fdw_exprs</>, and then at runtime the case works the
+     same as for an ordinary restriction clause.
+    </para>
+
   </sect1>
 
  </chapter>
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 868fb7130a8b28cf3e074d7d3903e58366c0c914..5cde22543f5b7d4d607224acfa22a604a419ed63 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -591,8 +591,9 @@ _copyForeignScan(const ForeignScan *from)
 	/*
 	 * copy remainder of node
 	 */
-	COPY_SCALAR_FIELD(fsSystemCol);
+	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
+	COPY_SCALAR_FIELD(fsSystemCol);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9daeb3e7b43e911aeab25b7521d41191928499bd..51181a9a7438e8609ab922340d1c2d20ba73726d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -559,8 +559,9 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 
 	_outScanInfo(str, (const Scan *) node);
 
-	WRITE_BOOL_FIELD(fsSystemCol);
+	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
+	WRITE_BOOL_FIELD(fsSystemCol);
 }
 
 static void
@@ -1741,6 +1742,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node)
 	WRITE_FLOAT_FIELD(allvisfrac, "%.6f");
 	WRITE_NODE_FIELD(subplan);
 	WRITE_NODE_FIELD(subroot);
+	/* we don't try to print fdwroutine or fdw_private */
 	WRITE_NODE_FIELD(baserestrictinfo);
 	WRITE_NODE_FIELD(joininfo);
 	WRITE_BOOL_FIELD(has_eclass_joins);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 6e81ce0fc26496f73e89d5048b0fd8a19da33b74..03c604a03d6f37d9d6d975fd0809429c56d61b38 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -396,6 +396,12 @@ set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 {
 	/* Mark rel with estimated output rows, width, etc */
 	set_foreign_size_estimates(root, rel);
+
+	/* Get FDW routine pointers for the rel */
+	rel->fdwroutine = GetFdwRoutineByRelId(rte->relid);
+
+	/* Let FDW adjust the size estimates, if it can */
+	rel->fdwroutine->GetForeignRelSize(root, rel, rte->relid);
 }
 
 /*
@@ -405,11 +411,8 @@ set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 static void
 set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 {
-	FdwRoutine *fdwroutine;
-
-	/* Call the FDW's PlanForeignScan function to generate path(s) */
-	fdwroutine = GetFdwRoutineByRelId(rte->relid);
-	fdwroutine->PlanForeignScan(rte->relid, root, rel);
+	/* Call the FDW's GetForeignPaths function to generate path(s) */
+	rel->fdwroutine->GetForeignPaths(root, rel, rte->relid);
 
 	/* Select cheapest path */
 	set_cheapest(rel);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 885d8558c319fd283df351c2c8e062a449b72d3c..24c853d47ef1aabb95156f90be4a8f3ea3d4995e 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -3745,7 +3745,7 @@ set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, Plan *cteplan)
  * using what will be purely datatype-driven estimates from the targetlist.
  * There is no way to do anything sane with the rows value, so we just put
  * a default estimate and hope that the wrapper can improve on it.	The
- * wrapper's PlanForeignScan function will be called momentarily.
+ * wrapper's GetForeignRelSize function will be called momentarily.
  *
  * The rel's targetlist and restrictinfo list must have been constructed
  * already.
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index b1df56cafd25abfda40657555a1c832aa6db797a..94140d304f754236955452e26b80de8276ca729b 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -20,6 +20,7 @@
 #include <math.h>
 
 #include "access/skey.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -119,8 +120,6 @@ static CteScan *make_ctescan(List *qptlist, List *qpqual,
 			 Index scanrelid, int ctePlanId, int cteParam);
 static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
 				   Index scanrelid, int wtParam);
-static ForeignScan *make_foreignscan(List *qptlist, List *qpqual,
-				 Index scanrelid, bool fsSystemCol, List *fdw_private);
 static BitmapAnd *make_bitmap_and(List *bitmapplans);
 static BitmapOr *make_bitmap_or(List *bitmapplans);
 static NestLoop *make_nestloop(List *tlist,
@@ -1816,7 +1815,6 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	RelOptInfo *rel = best_path->path.parent;
 	Index		scan_relid = rel->relid;
 	RangeTblEntry *rte;
-	bool		fsSystemCol;
 	int			i;
 
 	/* it should be a base rel... */
@@ -1825,31 +1823,56 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	rte = planner_rt_fetch(scan_relid, root);
 	Assert(rte->rtekind == RTE_RELATION);
 
-	/* Sort clauses into best execution order */
+	/*
+	 * Sort clauses into best execution order.  We do this first since the
+	 * FDW might have more info than we do and wish to adjust the ordering.
+	 */
 	scan_clauses = order_qual_clauses(root, scan_clauses);
 
-	/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
-	scan_clauses = extract_actual_clauses(scan_clauses, false);
+	/*
+	 * Let the FDW perform its processing on the restriction clauses and
+	 * generate the plan node.  Note that the FDW might remove restriction
+	 * clauses that it intends to execute remotely, or even add more (if it
+	 * has selected some join clauses for remote use but also wants them
+	 * rechecked locally).
+	 */
+	scan_plan = rel->fdwroutine->GetForeignPlan(root, rel, rte->relid,
+												best_path,
+												tlist, scan_clauses);
+
+	/* Copy cost data from Path to Plan; no need to make FDW do this */
+	copy_path_costsize(&scan_plan->scan.plan, &best_path->path);
 
-	/* Detect whether any system columns are requested from rel */
-	fsSystemCol = false;
+	/*
+	 * Replace any outer-relation variables with nestloop params in the qual
+	 * and fdw_exprs expressions.  We do this last so that the FDW doesn't
+	 * have to be involved.  (Note that parts of fdw_exprs could have come
+	 * from join clauses, so doing this beforehand on the scan_clauses
+	 * wouldn't work.)
+	 */
+	if (best_path->path.required_outer)
+	{
+		scan_plan->scan.plan.qual = (List *)
+			replace_nestloop_params(root, (Node *) scan_plan->scan.plan.qual);
+		scan_plan->fdw_exprs = (List *)
+			replace_nestloop_params(root, (Node *) scan_plan->fdw_exprs);
+	}
+
+	/*
+	 * Detect whether any system columns are requested from rel.  This is a
+	 * bit of a kluge and might go away someday, so we intentionally leave it
+	 * out of the API presented to FDWs.
+	 */
+	scan_plan->fsSystemCol = false;
 	for (i = rel->min_attr; i < 0; i++)
 	{
 		if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
 		{
-			fsSystemCol = true;
+			scan_plan->fsSystemCol = true;
 			break;
 		}
 	}
 
-	scan_plan = make_foreignscan(tlist,
-								 scan_clauses,
-								 scan_relid,
-								 fsSystemCol,
-								 best_path->fdw_private);
-
-	copy_path_costsize(&scan_plan->scan.plan, &best_path->path);
-
 	return scan_plan;
 }
 
@@ -3183,24 +3206,26 @@ make_worktablescan(List *qptlist,
 	return node;
 }
 
-static ForeignScan *
+ForeignScan *
 make_foreignscan(List *qptlist,
 				 List *qpqual,
 				 Index scanrelid,
-				 bool fsSystemCol,
+				 List *fdw_exprs,
 				 List *fdw_private)
 {
 	ForeignScan *node = makeNode(ForeignScan);
 	Plan	   *plan = &node->scan.plan;
 
-	/* cost should be inserted by caller */
+	/* cost will be filled in by create_foreignscan_plan */
 	plan->targetlist = qptlist;
 	plan->qual = qpqual;
 	plan->lefttree = NULL;
 	plan->righttree = NULL;
 	node->scan.scanrelid = scanrelid;
-	node->fsSystemCol = fsSystemCol;
+	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
+	/* fsSystemCol will be filled in by create_foreignscan_plan */
+	node->fsSystemCol = false;
 
 	return node;
 }
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index e1b48fb4f53061f5b06653c67275dc0b323bffd0..69396694aaa9df0edbc361ede98b3f77db6724dc 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -428,6 +428,8 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 					fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
 				splan->scan.plan.qual =
 					fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+				splan->fdw_exprs =
+					fix_scan_list(root, splan->fdw_exprs, rtoffset);
 			}
 			break;
 
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 40a420a3546f12de37459881ed587d89ae6954c3..b64db1e1c0659ea9b6af25327f689b083de845e6 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2137,6 +2137,8 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			break;
 
 		case T_ForeignScan:
+			finalize_primnode((Node *) ((ForeignScan *) plan)->fdw_exprs,
+							  &context);
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 6d1545476df7b054d48cae8a85669935d406c879..a2fc75a659e50ca7d5726ed9d024622af9e7f191 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1767,7 +1767,7 @@ create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel)
  *	  returning the pathnode.
  *
  * This function is never called from core Postgres; rather, it's expected
- * to be called by the PlanForeignScan function of a foreign data wrapper.
+ * to be called by the GetForeignPaths function of a foreign data wrapper.
  * We make the FDW supply all fields of the path, since we do not have any
  * way to calculate them in core.
  */
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 0cdf638c1ddb1c614eb8ef3cd2ddea4571248ff6..cee092a8810102fdf48023e180739cd8dbc3d81c 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -113,6 +113,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	rel->allvisfrac = 0;
 	rel->subplan = NULL;
 	rel->subroot = NULL;
+	rel->fdwroutine = NULL;
+	rel->fdw_private = NULL;
 	rel->baserestrictinfo = NIL;
 	rel->baserestrictcost.startup = 0;
 	rel->baserestrictcost.per_tuple = 0;
@@ -366,6 +368,8 @@ build_join_rel(PlannerInfo *root,
 	joinrel->allvisfrac = 0;
 	joinrel->subplan = NULL;
 	joinrel->subroot = NULL;
+	joinrel->fdwroutine = NULL;
+	joinrel->fdw_private = NULL;
 	joinrel->baserestrictinfo = NIL;
 	joinrel->baserestrictcost.startup = 0;
 	joinrel->baserestrictcost.per_tuple = 0;
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 9e135c62069fdc200e4f3cf58fa9725847d279fb..854f17755c4543ec85a66c6a6a3376fbcffd1cda 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -23,9 +23,20 @@ struct ExplainState;
  * Callback function signatures --- see fdwhandler.sgml for more info.
  */
 
-typedef void (*PlanForeignScan_function) (Oid foreigntableid,
-										  PlannerInfo *root,
-										  RelOptInfo *baserel);
+typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
+											RelOptInfo *baserel,
+											Oid foreigntableid);
+
+typedef void (*GetForeignPaths_function) (PlannerInfo *root,
+										  RelOptInfo *baserel,
+										  Oid foreigntableid);
+
+typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
+												 RelOptInfo *baserel,
+												 Oid foreigntableid,
+												 ForeignPath *best_path,
+												 List *tlist,
+												 List *scan_clauses);
 
 typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
 													struct ExplainState *es);
@@ -53,7 +64,9 @@ typedef struct FdwRoutine
 {
 	NodeTag		type;
 
-	PlanForeignScan_function PlanForeignScan;
+	GetForeignRelSize_function GetForeignRelSize;
+	GetForeignPaths_function GetForeignPaths;
+	GetForeignPlan_function GetForeignPlan;
 	ExplainForeignScan_function ExplainForeignScan;
 	BeginForeignScan_function BeginForeignScan;
 	IterateForeignScan_function IterateForeignScan;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 3962792d3d89a6d5077d1d682e2c3d112a6c568f..e6bb3239f4214c26aa1d70d4ca4ac50f63148ad5 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -462,13 +462,22 @@ typedef struct WorkTableScan
 
 /* ----------------
  *		ForeignScan node
+ *
+ * fdw_exprs and fdw_private are both under the control of the foreign-data
+ * wrapper, but fdw_exprs is presumed to contain expression trees and will
+ * be post-processed accordingly by the planner; fdw_private won't be.
+ * Note that everything in both lists must be copiable by copyObject().
+ * One way to store an arbitrary blob of bytes is to represent it as a bytea
+ * Const.  Usually, though, you'll be better off choosing a representation
+ * that can be dumped usefully by nodeToString().
  * ----------------
  */
 typedef struct ForeignScan
 {
 	Scan		scan;
-	bool		fsSystemCol;	/* true if any "system column" is needed */
+	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
+	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
 
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 2a686080059f3ffd26313798324323c6f2f6b56d..8616223f24a8cbe5424b89599b331538db05dd76 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -334,10 +334,13 @@ typedef struct PlannerInfo
  *		allvisfrac - fraction of disk pages that are marked all-visible
  *		subplan - plan for subquery (NULL if it's not a subquery)
  *		subroot - PlannerInfo for subquery (NULL if it's not a subquery)
+ *		fdwroutine - function hooks for FDW, if foreign table (else NULL)
+ *		fdw_private - private state for FDW, if foreign table (else NULL)
  *
  *		Note: for a subquery, tuples, subplan, subroot are not set immediately
  *		upon creation of the RelOptInfo object; they are filled in when
- *		set_base_rel_pathlist processes the object.
+ *		set_subquery_pathlist processes the object.  Likewise, fdwroutine
+ *		and fdw_private are filled during initial path creation.
  *
  *		For otherrels that are appendrel members, these fields are filled
  *		in just as for a baserel.
@@ -414,8 +417,12 @@ typedef struct RelOptInfo
 	BlockNumber pages;			/* size estimates derived from pg_class */
 	double		tuples;
 	double		allvisfrac;
+	/* use "struct Plan" to avoid including plannodes.h here */
 	struct Plan *subplan;		/* if subquery */
 	PlannerInfo *subroot;		/* if subquery */
+	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
+	struct FdwRoutine *fdwroutine;	/* if foreign table */
+	void	   *fdw_private;	/* if foreign table */
 
 	/* used by various scans and joins: */
 	List	   *baserestrictinfo;		/* RestrictInfo structures (if base
@@ -793,14 +800,13 @@ typedef struct TidPath
 } TidPath;
 
 /*
- * ForeignPath represents a scan of a foreign table
- *
- * fdw_private contains FDW private data about the scan, which will be copied
- * to the final ForeignScan plan node so that it is available at execution
- * time.  Note that everything in this list must be copiable by copyObject().
- * One way to store an arbitrary blob of bytes is to represent it as a bytea
- * Const.  Usually, though, you'll be better off choosing a representation
- * that can be dumped usefully by nodeToString().
+ * ForeignPath represents a potential scan of a foreign table
+ *
+ * fdw_private stores FDW private data about the scan.  While fdw_private is
+ * not actually touched by the core code during normal operations, it's
+ * generally a good idea to use a representation that can be dumped by
+ * nodeToString(), so that you can examine the structure during debugging
+ * with tools like pprint().
  */
 typedef struct ForeignPath
 {
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 8bd603124b3ac8ab87af160646301c7bbf8dbf16..47cc39cf1d9c3646fb10b76e6c4a97e166e65e08 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -42,6 +42,8 @@ extern Plan *optimize_minmax_aggregates(PlannerInfo *root, List *tlist,
 extern Plan *create_plan(PlannerInfo *root, Path *best_path);
 extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual,
 				  Index scanrelid, Plan *subplan);
+extern ForeignScan *make_foreignscan(List *qptlist, List *qpqual,
+				 Index scanrelid, List *fdw_exprs, List *fdw_private);
 extern Append *make_append(List *appendplans, List *tlist);
 extern RecursiveUnion *make_recursive_union(List *tlist,
 					 Plan *lefttree, Plan *righttree, int wtParam,