diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 4368897581a705f1e27ac6224060eef1f039b916..4c56444751b4f810de70bc10befccfa9e453252d 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -561,7 +561,8 @@ fileGetForeignPlan(PlannerInfo *root,
 							scan_clauses,
 							scan_relid,
 							NIL,	/* no expressions to evaluate */
-							best_path->fdw_private);
+							best_path->fdw_private,
+							NIL /* no custom tlist */ );
 }
 
 /*
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 173b4f06e65d1f0079238c4767689c8aaf3df90b..ec89b25f61a7ab8b4d9fe793821f5999e622d644 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -872,7 +872,8 @@ postgresGetForeignPlan(PlannerInfo *root,
 							local_exprs,
 							scan_relid,
 							params_list,
-							fdw_private);
+							fdw_private,
+							NIL /* no custom tlist */ );
 }
 
 /*
diff --git a/doc/src/sgml/custom-scan.sgml b/doc/src/sgml/custom-scan.sgml
index 9fd1db6fde48643feeaa92847f4c8c9150790965..62a8a3305bb29a5e7e405a8a1c5b391e1c8299eb 100644
--- a/doc/src/sgml/custom-scan.sgml
+++ b/doc/src/sgml/custom-scan.sgml
@@ -32,12 +32,13 @@
  </para>
 
  <sect1 id="custom-scan-path">
-  <title>Implementing Custom Paths</title>
+  <title>Creating Custom Scan Paths</title>
 
   <para>
-    A custom scan provider will typically add paths by setting the following
-    hook, which is called after the core code has generated what it believes
-    to be the complete and correct set of access paths for the relation.
+    A custom scan provider will typically add paths for a base relation by
+    setting the following hook, which is called after the core code has
+    generated what it believes to be the complete and correct set of access
+    paths for the relation.
 <programlisting>
 typedef void (*set_rel_pathlist_hook_type) (PlannerInfo *root,
                                             RelOptInfo *rel,
@@ -74,7 +75,7 @@ typedef struct CustomPath
     can support mark and restore.  Both capabilities are optional.
     <structfield>custom_private</> can be used to store the custom path's
     private data.  Private data should be stored in a form that can be handled
-    by <literal>nodeToString</>, so that debugging routines which attempt to
+    by <literal>nodeToString</>, so that debugging routines that attempt to
     print the custom path will work as designed.  <structfield>methods</> must
     point to a (usually statically allocated) object implementing the required
     custom path methods, of which there are currently only two, as further
@@ -82,29 +83,28 @@ typedef struct CustomPath
   </para>
 
   <para>
-   A custom scan provider can also add join paths; in this case, the scan
-   must produce the same output as would normally be produced by the join
-   it replaces.  To do this, the join provider should set the following hook.
-   This hook may be invoked repeatedly for the same pair of relations, with
-   different combinations of inner and outer relations; it is the
-   responsibility of the hook to minimize duplicated work.
+   A custom scan provider can also provide join paths.  Just as for base
+   relations, such a path must produce the same output as would normally be
+   produced by the join it replaces.  To do this, the join provider should
+   set the following hook, and then within the hook function,
+   create <structname>CustomPath</> path(s) for the join relation.
 <programlisting>
 typedef void (*set_join_pathlist_hook_type) (PlannerInfo *root,
                                              RelOptInfo *joinrel,
                                              RelOptInfo *outerrel,
                                              RelOptInfo *innerrel,
-                                             List *restrictlist,
                                              JoinType jointype,
-                                             SpecialJoinInfo *sjinfo,
-                                             SemiAntiJoinFactors *semifactors,
-                                             Relids param_source_rels,
-                                             Relids extra_lateral_rels);
+                                             JoinPathExtraData *extra);
 extern PGDLLIMPORT set_join_pathlist_hook_type set_join_pathlist_hook;
 </programlisting>
+
+   This hook will be invoked repeatedly for the same join relation, with
+   different combinations of inner and outer relations; it is the
+   responsibility of the hook to minimize duplicated work.
   </para>
 
   <sect2 id="custom-scan-path-callbacks">
-  <title>Custom Path Callbacks</title>
+  <title>Custom Scan Path Callbacks</title>
 
   <para>
 <programlisting>
@@ -125,7 +125,7 @@ void (*TextOutCustomPath) (StringInfo str,
                            const CustomPath *node);
 </programlisting>
     Generate additional output when <function>nodeToString</> is invoked on
-    this custom path.  This callback is optional. Since
+    this custom path.  This callback is optional.  Since
     <function>nodeToString</> will automatically dump all fields in the
     structure that it can see, including <structfield>custom_private</>, this
     is only useful if the <structname>CustomPath</> is actually embedded in a
@@ -135,7 +135,7 @@ void (*TextOutCustomPath) (StringInfo str,
  </sect1>
 
  <sect1 id="custom-scan-plan">
-  <title>Implementing Custom Plans</title>
+  <title>Creating Custom Scan Plans</title>
 
   <para>
     A custom scan is represented in a finished plan tree using the following
@@ -146,9 +146,9 @@ typedef struct CustomScan
     Scan      scan;
     uint32    flags;
     List     *custom_exprs;
-    List     *custom_ps_tlist;
     List     *custom_private;
-    List     *custom_relids;
+    List     *custom_scan_tlist;
+    Bitmapset *custom_relids;
     const CustomScanMethods *methods;
 } CustomScan;
 </programlisting>
@@ -158,16 +158,21 @@ typedef struct CustomScan
     <structfield>scan</> must be initialized as for any other scan, including
     estimated costs, target lists, qualifications, and so on.
     <structfield>flags</> is a bitmask with the same meaning as in
-    <structname>CustomPath</>.  <structfield>custom_exprs</> should be used to
+    <structname>CustomPath</>.
+    <structfield>custom_exprs</> should be used to
     store expression trees that will need to be fixed up by
     <filename>setrefs.c</> and <filename>subselect.c</>, while
-    <literal>custom_private</> should be used to store other private data that
-    is only used by the custom scan provider itself.  Plan trees must be able
-    to be duplicated using <function>copyObject</>, so all the data stored
-    within these two fields must consist of nodes that function can handle.
-    <literal>custom_relids</> is set by the core code to the set of relations
-    which this scan node must handle; except when this scan is replacing a
-    join, it will have only one member.
+    <structfield>custom_private</> should be used to store other private data
+    that is only used by the custom scan provider itself.
+    <structfield>custom_scan_tlist</> can be NIL when scanning a base
+    relation, indicating that the custom scan returns scan tuples that match
+    the base relation's rowtype.  Otherwise it is a targetlist describing
+    the actual scan tuples.  <structfield>custom_scan_tlist</> must be
+    provided for joins, and could be provided for scans if the custom scan
+    provider can compute some non-Var expressions.
+    <structfield>custom_relids</> is set by the core code to the set of
+    relations (rangetable indexes) that this scan node handles; except when
+    this scan is replacing a join, it will have only one member.
     <structfield>methods</> must point to a (usually statically allocated)
     object implementing the required custom scan methods, which are further
     detailed below.
@@ -175,19 +180,22 @@ typedef struct CustomScan
 
   <para>
    When a <structname>CustomScan</> scans a single relation,
-   <structfield>scan.scanrelid</> should be the range table index of the table
-   to be scanned, and <structfield>custom_ps_tlist</> should be
-   <literal>NULL</>.  When it replaces a join, <structfield>scan.scanrelid</>
-   should be zero, and <structfield>custom_ps_tlist</> should be a list of
-   <structname>TargetEntry</> nodes.  This is necessary because, when a join
-   is replaced, the target list cannot be constructed from the table
-   definition.  At execution time, this list will be used to initialize the
-   tuple descriptor of the <structname>TupleTableSlot</>.  It will also be
-   used by <command>EXPLAIN</>, when deparsing.
+   <structfield>scan.scanrelid</> must be the range table index of the table
+   to be scanned.  When it replaces a join, <structfield>scan.scanrelid</>
+   should be zero.
+  </para>
+
+  <para>
+   Plan trees must be able to be duplicated using <function>copyObject</>,
+   so all the data stored within the <quote>custom</> fields must consist of
+   nodes that that function can handle.  Furthermore, custom scan providers
+   cannot substitute a larger structure that embeds
+   a <structname>CustomScan</> for the structure itself, as would be possible
+   for a <structname>CustomPath</> or <structname>CustomScanState</>.
   </para>
 
   <sect2 id="custom-scan-plan-callbacks">
-   <title>Custom Scan Callbacks</title>
+   <title>Custom Scan Plan Callbacks</title>
    <para>
 <programlisting>
 Node *(*CreateCustomScanState) (CustomScan *cscan);
@@ -195,12 +203,12 @@ Node *(*CreateCustomScanState) (CustomScan *cscan);
     Allocate a <structname>CustomScanState</> for this
     <structname>CustomScan</>.  The actual allocation will often be larger than
     required for an ordinary <structname>CustomScanState</>, because many
-    scan types will wish to embed that as the first field of a large structure.
+    providers will wish to embed that as the first field of a larger structure.
     The value returned must have the node tag and <structfield>methods</>
-    set appropriately, but the other fields need not be initialized at this
+    set appropriately, but other fields should be left as zeroes at this
     stage; after <function>ExecInitCustomScan</> performs basic initialization,
     the <function>BeginCustomScan</> callback will be invoked to give the
-    custom scan state a chance to do whatever else is needed.
+    custom scan provider a chance to do whatever else is needed.
    </para>
 
    <para>
@@ -209,23 +217,21 @@ void (*TextOutCustomScan) (StringInfo str,
                            const CustomScan *node);
 </programlisting>
     Generate additional output when <function>nodeToString</> is invoked on
-    this custom plan.  This callback is optional.  Since a
-    <structname>CustomScan</> must be copyable by <function>copyObject</>,
-    custom scan providers cannot substitute a larger structure that embeds a
-    <structname>CustomScan</> for the structure itself, as would be possible
-    for a <structname>CustomPath</> or <structname>CustomScanState</>.
-    Therefore, providing this callback is unlikely to be useful.
+    this custom plan node.  This callback is optional.  Since
+    <function>nodeToString</> will automatically dump all fields in the
+    structure, including the substructure of the <quote>custom</> fields,
+    there is usually not much need for this callback.
    </para>
   </sect2>
  </sect1>
 
- <sect1 id="custom-scan-scan">
-  <title>Implementing Custom Scans</title>
+ <sect1 id="custom-scan-execution">
+  <title>Executing Custom Scans</title>
 
   <para>
    When a <structfield>CustomScan</> is executed, its execution state is
    represented by a <structfield>CustomScanState</>, which is declared as
-   follows.
+   follows:
 <programlisting>
 typedef struct CustomScanState
 {
@@ -237,7 +243,9 @@ typedef struct CustomScanState
   </para>
 
   <para>
-   <structfield>ss</> must be initialized as for any other scanstate;
+   <structfield>ss</> is initialized as for any other scanstate,
+   except that if the scan is for a join rather than a base relation,
+   <literal>ss.ss_currentRelation</> is left NULL.
    <structfield>flags</> is a bitmask with the same meaning as in
    <structname>CustomPath</> and <structname>CustomScan</>.
    <structfield>methods</> must point to a (usually statically allocated)
@@ -247,8 +255,8 @@ typedef struct CustomScanState
    structure embedding the above as its first member.
   </para>
 
-  <sect2 id="custom-scan-scan-callbacks">
-   <title>Custom Execution-Time Callbacks</title>
+  <sect2 id="custom-scan-execution-callbacks">
+   <title>Custom Scan Execution Callbacks</title>
 
    <para>
 <programlisting>
@@ -257,8 +265,8 @@ void (*BeginCustomScan) (CustomScanState *node,
                          int eflags);
 </programlisting>
     Complete initialization of the supplied <structname>CustomScanState</>.
-    Some initialization is performed by <function>ExecInitCustomScan</>, but
-    any private fields should be initialized here.
+    Standard fields have been initialized by <function>ExecInitCustomScan</>,
+    but any private fields should be initialized here.
    </para>
 
    <para>
@@ -276,8 +284,8 @@ TupleTableSlot *(*ExecCustomScan) (CustomScanState *node);
 void (*EndCustomScan) (CustomScanState *node);
 </programlisting>
     Clean up any private data associated with the <literal>CustomScanState</>.
-    This method is required, but may not need to do anything if the associated
-    data does not exist or will be cleaned up automatically.
+    This method is required, but it does not need to do anything if there is
+    no associated data or it will be cleaned up automatically.
    </para>
 
    <para>
@@ -293,8 +301,8 @@ void (*ReScanCustomScan) (CustomScanState *node);
 void (*MarkPosCustomScan) (CustomScanState *node);
 </programlisting>
     Save the current scan position so that it can subsequently be restored
-    by the <function>RestrPosCustomScan</> callback.  This calback is optional,
-    and need only be supplied if 
+    by the <function>RestrPosCustomScan</> callback.  This callback is
+    optional, and need only be supplied if the
     <literal>CUSTOMPATH_SUPPORT_MARK_RESTORE</> flag is set.
    </para>
 
@@ -304,7 +312,7 @@ void (*RestrPosCustomScan) (CustomScanState *node);
 </programlisting>
     Restore the previous scan position as saved by the
     <function>MarkPosCustomScan</> callback.  This callback is optional,
-    and need only be supplied if 
+    and need only be supplied if the
     <literal>CUSTOMPATH_SUPPORT_MARK_RESTORE</> flag is set.
    </para>
 
@@ -314,8 +322,8 @@ void (*ExplainCustomScan) (CustomScanState *node,
                            List *ancestors,
                            ExplainState *es);
 </programlisting>
-    Output additional information on <command>EXPLAIN</> that involves
-    custom-scan node.  This callback is optional.  Common data stored in the
+    Output additional information for <command>EXPLAIN</> of a custom-scan
+    plan node.  This callback is optional.  Common data stored in the
     <structname>ScanState</>, such as the target list and scan relation, will
     be shown even without this callback, but the callback allows the display
     of additional, private state.
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index bc06d2cbb26f8c9d28faa5696bcd1659c011ea7b..33863f04f82899bf1f306e8fc7777527f23e479c 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -175,8 +175,11 @@ GetForeignPlan (PlannerInfo *root,
      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.
+     <function>GetForeignPaths</> or <function>GetForeignJoinPaths</>),
+     the target list to be emitted by the plan node,
+     and the restriction clauses to be enforced by the plan node.
+     (If the path is for a join rather than a base
+     relation, <literal>foreigntableid</> is <literal>InvalidOid</>.)
     </para>
 
     <para>
@@ -235,9 +238,12 @@ IterateForeignScan (ForeignScanState *node);
     </para>
 
     <para>
-     The rows returned must match the column signature of the foreign table
-     being scanned.  If you choose to optimize away fetching columns that
-     are not needed, you should insert nulls in those column positions.
+     The rows returned must match the <structfield>fdw_scan_tlist</> target
+     list if one was supplied, otherwise they must match the rowtype of the
+     foreign table being scanned.  If you choose to optimize away fetching
+     columns that are not needed, you should insert nulls in those column
+     positions, or else generate a <structfield>fdw_scan_tlist</> list with
+     those columns omitted.
     </para>
 
     <para>
@@ -275,6 +281,67 @@ EndForeignScan (ForeignScanState *node);
 
    </sect2>
 
+   <sect2 id="fdw-callbacks-join-scan">
+    <title>FDW Routines For Scanning Foreign Joins</title>
+
+    <para>
+     If an FDW supports performing foreign joins remotely (rather than
+     by fetching both tables' data and doing the join locally), it should
+     provide this callback function:
+    </para>
+
+    <para>
+<programlisting>
+void
+GetForeignJoinPaths (PlannerInfo *root,
+                     RelOptInfo *joinrel,
+                     RelOptInfo *outerrel,
+                     RelOptInfo *innerrel,
+                     JoinType jointype,
+                     JoinPathExtraData *extra);
+</programlisting>
+     Create possible access paths for a join of two (or more) foreign tables
+     that all belong to the same foreign server.  This optional
+     function is called during query planning.  As
+     with <function>GetForeignPaths</>, this function should
+     generate <structname>ForeignPath</> path(s) for the
+     supplied <literal>joinrel</>, and call <function>add_path</> to add these
+     paths to the set of paths considered for the join.  But unlike
+     <function>GetForeignPaths</>, it is not necessary that this function
+     succeed in creating at least one path, since paths involving local
+     joining are always possible.
+    </para>
+
+    <para>
+     Note that this function will be invoked repeatedly for the same join
+     relation, with different combinations of inner and outer relations; it is
+     the responsibility of the FDW to minimize duplicated work.
+    </para>
+
+    <para>
+     If a <structname>ForeignPath</> path is chosen for the join, it will
+     represent the entire join process; paths generated for the component
+     tables and subsidiary joins will not be used.  Subsequent processing of
+     the join path proceeds much as it does for a path scanning a single
+     foreign table.  One difference is that the <structfield>scanrelid</> of
+     the resulting <structname>ForeignScan</> plan node should be set to zero,
+     since there is no single relation that it represents; instead,
+     the <structfield>fs_relids</> field of the <structname>ForeignScan</>
+     node represents the set of relations that were joined.  (The latter field
+     is set up automatically by the core planner code, and need not be filled
+     by the FDW.)  Another difference is that, because the column list for a
+     remote join cannot be found from the system catalogs, the FDW must
+     fill <structfield>fdw_scan_tlist</> with an appropriate list
+     of <structfield>TargetEntry</> nodes, representing the set of columns
+     it will supply at runtime in the tuples it returns.
+    </para>
+
+    <para>
+     See <xref linkend="fdw-planning"> for additional information.
+    </para>
+
+   </sect2>
+
    <sect2 id="fdw-callbacks-update">
     <title>FDW Routines For Updating Foreign Tables</title>
 
@@ -598,42 +665,6 @@ IsForeignRelUpdatable (Relation rel);
 
    </sect2>
 
-   <sect2>
-    <title>FDW Routines For Remote Joins</title>
-    <para>
-<programlisting>
-void
-GetForeignJoinPaths(PlannerInfo *root,
-                    RelOptInfo *joinrel,
-                    RelOptInfo *outerrel,
-                    RelOptInfo *innerrel,
-                    List *restrictlist,
-                    JoinType jointype,
-                    SpecialJoinInfo *sjinfo,
-                    SemiAntiJoinFactors *semifactors,
-                    Relids param_source_rels,
-                    Relids extra_lateral_rels);
-</programlisting>
-     Create possible access paths for a join of two foreign tables managed
-     by the same foreign data wrapper.
-     This optional function is called during query planning.
-    </para>
-    <para>
-     This function the FDW to add <structname>ForeignScan</> paths for the
-     supplied <literal>joinrel</>.  Typically, the FDW will send the whole
-     join to the remote server as a single query, as performing the join
-     remotely rather than locally is typically much more efficient.
-    </para>
-    <para>
-     Since we cannot construct the slot descriptor for a remote join from
-     the catalogs, the FDW should set the <structfield>scanrelid</> of the
-     <structname>ForeignScan</> to zero and <structfield>fdw_ps_tlist</>
-     to an appropriate list of <structfield>TargetEntry</> nodes.
-     Junk entries will be ignored, but can be present for the benefit of
-     deparsing performed by <command>EXPLAIN</>.
-    </para>
-   </sect2>
-
    <sect2 id="fdw-callbacks-explain">
     <title>FDW Routines for <command>EXPLAIN</></title>
 
@@ -904,10 +935,10 @@ GetForeignServerByName(const char *name, bool missing_ok);
 
     <para>
      The FDW callback functions <function>GetForeignRelSize</>,
-     <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
-     <function>PlanForeignModify</> must fit into the workings of the
-     <productname>PostgreSQL</> planner.  Here are some notes about what
-     they must do.
+     <function>GetForeignPaths</>, <function>GetForeignPlan</>,
+     <function>PlanForeignModify</>, and <function>GetForeignJoinPaths</>
+     must fit into the workings of the <productname>PostgreSQL</> planner.
+     Here are some notes about what they must do.
     </para>
 
     <para>
@@ -934,7 +965,7 @@ GetForeignServerByName(const char *name, bool missing_ok);
      <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.
+     to initialize it to NULL when the <literal>RelOptInfo</> node is created.
      It is useful for passing information forward from
      <function>GetForeignRelSize</> to <function>GetForeignPaths</> and/or
      <function>GetForeignPaths</> to <function>GetForeignPlan</>, thereby
@@ -1002,6 +1033,23 @@ GetForeignServerByName(const char *name, bool missing_ok);
      evaluation of the <structfield>fdw_exprs</> expression tree.
     </para>
 
+    <para>
+     Another <structname>ForeignScan</> field that can be filled by FDWs
+     is <structfield>fdw_scan_tlist</>, which describes the tuples returned by
+     the FDW for this plan node.  For simple foreign table scans this can be
+     set to <literal>NIL</>, implying that the returned tuples have the
+     rowtype declared for the foreign table.  A non-NIL value must be a
+     targetlist (list of <structname>TargetEntry</>s) containing Vars and/or
+     expressions representing the returned columns.  This might be used, for
+     example, to show that the FDW has omitted some columns that it noticed
+     won't be needed for the query.  Also, if the FDW can compute expressions
+     used by the query more cheaply than can be done locally, it could add
+     those expressions to <structfield>fdw_scan_tlist</>.  Note that join
+     plans (created from paths made by <function>GetForeignJoinPaths</>) must
+     always supply <structfield>fdw_scan_tlist</> to describe the set of
+     columns they will return.
+    </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
@@ -1019,6 +1067,18 @@ GetForeignServerByName(const char *name, bool missing_ok);
      same as for an ordinary restriction clause.
     </para>
 
+    <para>
+     If an FDW supports remote joins, <function>GetForeignJoinPaths</> should
+     produce <structname>ForeignPath</>s for potential remote joins in much
+     the same way as <function>GetForeignPaths</> works for base tables.
+     Information about the intended join can be passed forward
+     to <function>GetForeignPlan</> in the same ways described above.
+     However, <structfield>baserestrictinfo</> is not relevant for join
+     relations; instead, the relevant join clauses for a particular join are
+     passed to <function>GetForeignJoinPaths</> as a separate parameter
+     (<literal>extra-&gt;restrictlist</>).
+    </para>
+
     <para>
      When planning an <command>UPDATE</> or <command>DELETE</>,
      <function>PlanForeignModify</> can look up the <structname>RelOptInfo</>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index c5452e3cb6ab7440d75b429d20a3651c275bfe59..eeb8f19017efcae22937b44a6a07b5a276fa6bf2 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -736,11 +736,11 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 			break;
 		case T_ForeignScan:
 			*rels_used = bms_add_members(*rels_used,
-										 ((ForeignScan *) plan)->fdw_relids);
+										 ((ForeignScan *) plan)->fs_relids);
 			break;
 		case T_CustomScan:
 			*rels_used = bms_add_members(*rels_used,
-										 ((CustomScan *) plan)->custom_relids);
+									   ((CustomScan *) plan)->custom_relids);
 			break;
 		case T_ModifyTable:
 			*rels_used = bms_add_member(*rels_used,
diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c
index fa475014f134d527498c5481573c788678a4f0b5..a96e826ba4257a843f478a4d22582196162f40a6 100644
--- a/src/backend/executor/execScan.c
+++ b/src/backend/executor/execScan.c
@@ -246,19 +246,18 @@ void
 ExecAssignScanProjectionInfo(ScanState *node)
 {
 	Scan	   *scan = (Scan *) node->ps.plan;
-	Index		varno;
 
-	/* Vars in an index-only scan's tlist should be INDEX_VAR */
-	if (IsA(scan, IndexOnlyScan))
-		varno = INDEX_VAR;
-	/* Also foreign or custom scan on pseudo relation should be INDEX_VAR */
-	else if (scan->scanrelid == 0)
-	{
-		Assert(IsA(scan, ForeignScan) || IsA(scan, CustomScan));
-		varno = INDEX_VAR;
-	}
-	else
-		varno = scan->scanrelid;
+	ExecAssignScanProjectionInfoWithVarno(node, scan->scanrelid);
+}
+
+/*
+ * ExecAssignScanProjectionInfoWithVarno
+ *		As above, but caller can specify varno expected in Vars in the tlist.
+ */
+void
+ExecAssignScanProjectionInfoWithVarno(ScanState *node, Index varno)
+{
+	Scan	   *scan = (Scan *) node->ps.plan;
 
 	if (tlist_matches_tupdesc(&node->ps,
 							  scan->plan.targetlist,
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
index db1b4f2ffa4dbaffcff031277815d5b77c31014b..0a022dff940b7627191831e5648075747a53e832 100644
--- a/src/backend/executor/nodeCustom.c
+++ b/src/backend/executor/nodeCustom.c
@@ -22,13 +22,24 @@
 CustomScanState *
 ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
 {
-	CustomScanState    *css;
-	Index				scan_relid = cscan->scan.scanrelid;
+	CustomScanState *css;
+	Relation	scan_rel = NULL;
+	Index		scanrelid = cscan->scan.scanrelid;
+	Index		tlistvarno;
 
-	/* populate a CustomScanState according to the CustomScan */
+	/*
+	 * Allocate the CustomScanState object.  We let the custom scan provider
+	 * do the palloc, in case it wants to make a larger object that embeds
+	 * CustomScanState as the first field.  It must set the node tag and the
+	 * methods field correctly at this time.  Other standard fields should be
+	 * set to zero.
+	 */
 	css = (CustomScanState *) cscan->methods->CreateCustomScanState(cscan);
 	Assert(IsA(css, CustomScanState));
 
+	/* ensure flags is filled correctly */
+	css->flags = cscan->flags;
+
 	/* fill up fields of ScanState */
 	css->ss.ps.plan = &cscan->scan.plan;
 	css->ss.ps.state = estate;
@@ -36,6 +47,8 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
 	/* create expression context for node */
 	ExecAssignExprContext(estate, &css->ss.ps);
 
+	css->ss.ps.ps_TupFromTlist = false;
+
 	/* initialize child expressions */
 	css->ss.ps.targetlist = (List *)
 		ExecInitExpr((Expr *) cscan->scan.plan.targetlist,
@@ -49,32 +62,40 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
 	ExecInitResultTupleSlot(estate, &css->ss.ps);
 
 	/*
-	 * open the base relation and acquire an appropriate lock on it;
-	 * also, get and assign the scan type
+	 * open the base relation, if any, and acquire an appropriate lock on it
 	 */
-	if (scan_relid > 0)
+	if (scanrelid > 0)
 	{
-		Relation		scan_rel;
-
-		scan_rel = ExecOpenScanRelation(estate, scan_relid, eflags);
+		scan_rel = ExecOpenScanRelation(estate, scanrelid, eflags);
 		css->ss.ss_currentRelation = scan_rel;
-		css->ss.ss_currentScanDesc = NULL;	/* set by provider */
-		ExecAssignScanType(&css->ss, RelationGetDescr(scan_rel));
 	}
-	else
+
+	/*
+	 * Determine the scan tuple type.  If the custom scan provider provided a
+	 * targetlist describing the scan tuples, use that; else use base
+	 * relation's rowtype.
+	 */
+	if (cscan->custom_scan_tlist != NIL || scan_rel == NULL)
 	{
-		TupleDesc	ps_tupdesc;
+		TupleDesc	scan_tupdesc;
 
-		ps_tupdesc = ExecCleanTypeFromTL(cscan->custom_ps_tlist, false);
-		ExecAssignScanType(&css->ss, ps_tupdesc);
+		scan_tupdesc = ExecTypeFromTL(cscan->custom_scan_tlist, false);
+		ExecAssignScanType(&css->ss, scan_tupdesc);
+		/* Node's targetlist will contain Vars with varno = INDEX_VAR */
+		tlistvarno = INDEX_VAR;
+	}
+	else
+	{
+		ExecAssignScanType(&css->ss, RelationGetDescr(scan_rel));
+		/* Node's targetlist will contain Vars with varno = scanrelid */
+		tlistvarno = scanrelid;
 	}
-	css->ss.ps.ps_TupFromTlist = false;
 
 	/*
 	 * Initialize result tuple type and projection info.
 	 */
 	ExecAssignResultTypeFromTL(&css->ss.ps);
-	ExecAssignScanProjectionInfo(&css->ss);
+	ExecAssignScanProjectionInfoWithVarno(&css->ss, tlistvarno);
 
 	/*
 	 * The callback of custom-scan provider applies the final initialization
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index fa553ace5d687960f4aefc5124834397041018ca..bb28a7372d1be4a8dfc4cc9bd378d531af3a745d 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -102,7 +102,9 @@ ForeignScanState *
 ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 {
 	ForeignScanState *scanstate;
+	Relation	currentRelation = NULL;
 	Index		scanrelid = node->scan.scanrelid;
+	Index		tlistvarno;
 	FdwRoutine *fdwroutine;
 
 	/* check for unsupported flags */
@@ -141,40 +143,55 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	ExecInitScanTupleSlot(estate, &scanstate->ss);
 
 	/*
-	 * open the base relation and acquire an appropriate lock on it;
-	 * also, get and assign the scan type
+	 * open the base relation, if any, and acquire an appropriate lock on it;
+	 * also acquire function pointers from the FDW's handler
 	 */
 	if (scanrelid > 0)
 	{
-		Relation	currentRelation;
-
 		currentRelation = ExecOpenScanRelation(estate, scanrelid, eflags);
 		scanstate->ss.ss_currentRelation = currentRelation;
-		ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+		fdwroutine = GetFdwRoutineForRelation(currentRelation, true);
 	}
 	else
 	{
-		TupleDesc	ps_tupdesc;
+		/* We can't use the relcache, so get fdwroutine the hard way */
+		fdwroutine = GetFdwRoutineByServerId(node->fs_server);
+	}
 
-		ps_tupdesc = ExecCleanTypeFromTL(node->fdw_ps_tlist, false);
-		ExecAssignScanType(&scanstate->ss, ps_tupdesc);
+	/*
+	 * Determine the scan tuple type.  If the FDW provided a targetlist
+	 * describing the scan tuples, use that; else use base relation's rowtype.
+	 */
+	if (node->fdw_scan_tlist != NIL || currentRelation == NULL)
+	{
+		TupleDesc	scan_tupdesc;
+
+		scan_tupdesc = ExecTypeFromTL(node->fdw_scan_tlist, false);
+		ExecAssignScanType(&scanstate->ss, scan_tupdesc);
+		/* Node's targetlist will contain Vars with varno = INDEX_VAR */
+		tlistvarno = INDEX_VAR;
+	}
+	else
+	{
+		ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+		/* Node's targetlist will contain Vars with varno = scanrelid */
+		tlistvarno = scanrelid;
 	}
 
 	/*
 	 * Initialize result tuple type and projection info.
 	 */
 	ExecAssignResultTypeFromTL(&scanstate->ss.ps);
-	ExecAssignScanProjectionInfo(&scanstate->ss);
+	ExecAssignScanProjectionInfoWithVarno(&scanstate->ss, tlistvarno);
 
 	/*
-	 * Acquire function pointers from the FDW's handler, and init fdw_state.
+	 * Initialize FDW-related state.
 	 */
-	fdwroutine = GetFdwRoutine(node->fdw_handler);
 	scanstate->fdwroutine = fdwroutine;
 	scanstate->fdw_state = NULL;
 
 	/*
-	 * Tell the FDW to initiate the scan.
+	 * Tell the FDW to initialize the scan.
 	 */
 	fdwroutine->BeginForeignScan(scanstate, eflags);
 
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index 06b7c3c457abc5f4f12eefac4f5d1290bec06586..61bd644ab7135c446e3b467c455f415a917f5cde 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -442,10 +442,12 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
 	ExecAssignScanType(&indexstate->ss, tupDesc);
 
 	/*
-	 * Initialize result tuple type and projection info.
+	 * Initialize result tuple type and projection info.  The node's
+	 * targetlist will contain Vars with varno = INDEX_VAR, referencing the
+	 * scan tuple.
 	 */
 	ExecAssignResultTypeFromTL(&indexstate->ss.ps);
-	ExecAssignScanProjectionInfo(&indexstate->ss);
+	ExecAssignScanProjectionInfoWithVarno(&indexstate->ss, INDEX_VAR);
 
 	/*
 	 * If we are just doing EXPLAIN (ie, aren't going to run the plan), stop
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index cdbd550fd4363a17d394df04018b1724682a9175..763ee7c9bcea0b9aa9e654cfec2a6d89202d828d 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -304,21 +304,16 @@ GetFdwRoutine(Oid fdwhandler)
 
 
 /*
- * GetFdwHandlerByRelId - look up the handler of the foreign-data wrapper
- * for the given foreign table
+ * GetForeignServerIdByRelId - look up the foreign server
+ * for the given foreign table, and return its OID.
  */
 Oid
-GetFdwHandlerByRelId(Oid relid)
+GetForeignServerIdByRelId(Oid relid)
 {
 	HeapTuple	tp;
-	Form_pg_foreign_data_wrapper fdwform;
-	Form_pg_foreign_server serverform;
 	Form_pg_foreign_table tableform;
 	Oid			serverid;
-	Oid			fdwid;
-	Oid			fdwhandler;
 
-	/* Get server OID for the foreign table. */
 	tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(tp))
 		elog(ERROR, "cache lookup failed for foreign table %u", relid);
@@ -326,6 +321,23 @@ GetFdwHandlerByRelId(Oid relid)
 	serverid = tableform->ftserver;
 	ReleaseSysCache(tp);
 
+	return serverid;
+}
+
+
+/*
+ * GetFdwRoutineByServerId - look up the handler of the foreign-data wrapper
+ * for the given foreign server, and retrieve its FdwRoutine struct.
+ */
+FdwRoutine *
+GetFdwRoutineByServerId(Oid serverid)
+{
+	HeapTuple	tp;
+	Form_pg_foreign_data_wrapper fdwform;
+	Form_pg_foreign_server serverform;
+	Oid			fdwid;
+	Oid			fdwhandler;
+
 	/* Get foreign-data wrapper OID for the server. */
 	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
 	if (!HeapTupleIsValid(tp))
@@ -350,9 +362,11 @@ GetFdwHandlerByRelId(Oid relid)
 
 	ReleaseSysCache(tp);
 
-	return fdwhandler;
+	/* And finally, call the handler function. */
+	return GetFdwRoutine(fdwhandler);
 }
 
+
 /*
  * GetFdwRoutineByRelId - look up the handler of the foreign-data wrapper
  * for the given foreign table, and retrieve its FdwRoutine struct.
@@ -360,9 +374,13 @@ GetFdwHandlerByRelId(Oid relid)
 FdwRoutine *
 GetFdwRoutineByRelId(Oid relid)
 {
-	Oid			fdwhandler = GetFdwHandlerByRelId(relid);
+	Oid			serverid;
 
-	return GetFdwRoutine(fdwhandler);
+	/* Get server OID for the foreign table. */
+	serverid = GetForeignServerIdByRelId(relid);
+
+	/* Now retrieve server's FdwRoutine struct. */
+	return GetFdwRoutineByServerId(serverid);
 }
 
 /*
@@ -656,7 +674,7 @@ get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok)
 
 
 /*
- * get_foreign_server_oid - given a FDW name, look up the OID
+ * get_foreign_server_oid - given a server name, look up the OID
  *
  * If missing_ok is false, throw an error if name not found.  If true, just
  * return InvalidOid.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a3139d3eb5db5c6ad6250a9db3fb58c9a5f8946a..ed8fa72621ece43657d7d647600946f7d0e3ec2d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -599,11 +599,11 @@ _copyForeignScan(const ForeignScan *from)
 	/*
 	 * copy remainder of node
 	 */
-	COPY_SCALAR_FIELD(fdw_handler);
+	COPY_SCALAR_FIELD(fs_server);
 	COPY_NODE_FIELD(fdw_exprs);
-	COPY_NODE_FIELD(fdw_ps_tlist);
 	COPY_NODE_FIELD(fdw_private);
-	COPY_BITMAPSET_FIELD(fdw_relids);
+	COPY_NODE_FIELD(fdw_scan_tlist);
+	COPY_BITMAPSET_FIELD(fs_relids);
 	COPY_SCALAR_FIELD(fsSystemCol);
 
 	return newnode;
@@ -627,8 +627,8 @@ _copyCustomScan(const CustomScan *from)
 	 */
 	COPY_SCALAR_FIELD(flags);
 	COPY_NODE_FIELD(custom_exprs);
-	COPY_NODE_FIELD(custom_ps_tlist);
 	COPY_NODE_FIELD(custom_private);
+	COPY_NODE_FIELD(custom_scan_tlist);
 	COPY_BITMAPSET_FIELD(custom_relids);
 
 	/*
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index bc891d391f56943030cfa27c20b77eed6da005f4..fe868b889d96a93c025ae6871a6dec3f7726fbc3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -565,11 +565,11 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 
 	_outScanInfo(str, (const Scan *) node);
 
-	WRITE_OID_FIELD(fdw_handler);
+	WRITE_OID_FIELD(fs_server);
 	WRITE_NODE_FIELD(fdw_exprs);
-	WRITE_NODE_FIELD(fdw_ps_tlist);
 	WRITE_NODE_FIELD(fdw_private);
-	WRITE_BITMAPSET_FIELD(fdw_relids);
+	WRITE_NODE_FIELD(fdw_scan_tlist);
+	WRITE_BITMAPSET_FIELD(fs_relids);
 	WRITE_BOOL_FIELD(fsSystemCol);
 }
 
@@ -582,8 +582,8 @@ _outCustomScan(StringInfo str, const CustomScan *node)
 
 	WRITE_UINT_FIELD(flags);
 	WRITE_NODE_FIELD(custom_exprs);
-	WRITE_NODE_FIELD(custom_ps_tlist);
 	WRITE_NODE_FIELD(custom_private);
+	WRITE_NODE_FIELD(custom_scan_tlist);
 	WRITE_BITMAPSET_FIELD(custom_relids);
 	appendStringInfoString(str, " :methods ");
 	_outToken(str, node->methods->CustomName);
@@ -1844,6 +1844,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node)
 	WRITE_NODE_FIELD(subplan);
 	WRITE_NODE_FIELD(subroot);
 	WRITE_NODE_FIELD(subplan_params);
+	WRITE_OID_FIELD(serverid);
 	/* we don't try to print fdwroutine or fdw_private */
 	WRITE_NODE_FIELD(baserestrictinfo);
 	WRITE_NODE_FIELD(joininfo);
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index dabef3c3c7fe4ff5df5b3c3c67c15cdd13186d1e..ba78252b8f998e03ca23fb87212d228f71fbb877 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -30,21 +30,13 @@ set_join_pathlist_hook_type set_join_pathlist_hook = NULL;
 
 static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
 					 RelOptInfo *outerrel, RelOptInfo *innerrel,
-					 List *restrictlist, List *mergeclause_list,
-					 JoinType jointype, SpecialJoinInfo *sjinfo,
-					 Relids param_source_rels, Relids extra_lateral_rels);
+					 JoinType jointype, JoinPathExtraData *extra);
 static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
 					 RelOptInfo *outerrel, RelOptInfo *innerrel,
-					 List *restrictlist, List *mergeclause_list,
-					 JoinType jointype, SpecialJoinInfo *sjinfo,
-					 SemiAntiJoinFactors *semifactors,
-					 Relids param_source_rels, Relids extra_lateral_rels);
+					 JoinType jointype, JoinPathExtraData *extra);
 static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
 					 RelOptInfo *outerrel, RelOptInfo *innerrel,
-					 List *restrictlist,
-					 JoinType jointype, SpecialJoinInfo *sjinfo,
-					 SemiAntiJoinFactors *semifactors,
-					 Relids param_source_rels, Relids extra_lateral_rels);
+					 JoinType jointype, JoinPathExtraData *extra);
 static List *select_mergejoin_clauses(PlannerInfo *root,
 						 RelOptInfo *joinrel,
 						 RelOptInfo *outerrel,
@@ -86,13 +78,16 @@ add_paths_to_joinrel(PlannerInfo *root,
 					 SpecialJoinInfo *sjinfo,
 					 List *restrictlist)
 {
-	List	   *mergeclause_list = NIL;
+	JoinPathExtraData extra;
 	bool		mergejoin_allowed = true;
-	SemiAntiJoinFactors semifactors;
-	Relids		param_source_rels = NULL;
-	Relids		extra_lateral_rels = NULL;
 	ListCell   *lc;
 
+	extra.restrictlist = restrictlist;
+	extra.mergeclause_list = NIL;
+	extra.sjinfo = sjinfo;
+	extra.param_source_rels = NULL;
+	extra.extra_lateral_rels = NULL;
+
 	/*
 	 * Find potential mergejoin clauses.  We can skip this if we are not
 	 * interested in doing a mergejoin.  However, mergejoin may be our only
@@ -100,13 +95,13 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 * it's a full join.
 	 */
 	if (enable_mergejoin || jointype == JOIN_FULL)
-		mergeclause_list = select_mergejoin_clauses(root,
-													joinrel,
-													outerrel,
-													innerrel,
-													restrictlist,
-													jointype,
-													&mergejoin_allowed);
+		extra.mergeclause_list = select_mergejoin_clauses(root,
+														  joinrel,
+														  outerrel,
+														  innerrel,
+														  restrictlist,
+														  jointype,
+														  &mergejoin_allowed);
 
 	/*
 	 * If it's SEMI or ANTI join, compute correction factors for cost
@@ -115,7 +110,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 	if (jointype == JOIN_SEMI || jointype == JOIN_ANTI)
 		compute_semi_anti_join_factors(root, outerrel, innerrel,
 									   jointype, sjinfo, restrictlist,
-									   &semifactors);
+									   &extra.semifactors);
 
 	/*
 	 * Decide whether it's sensible to generate parameterized paths for this
@@ -142,16 +137,16 @@ add_paths_to_joinrel(PlannerInfo *root,
 		 */
 		if (bms_overlap(joinrel->relids, sjinfo->min_righthand) &&
 			!bms_overlap(joinrel->relids, sjinfo->min_lefthand))
-			param_source_rels = bms_join(param_source_rels,
-										 bms_difference(root->all_baserels,
+			extra.param_source_rels = bms_join(extra.param_source_rels,
+										   bms_difference(root->all_baserels,
 													 sjinfo->min_righthand));
 
 		/* full joins constrain both sides symmetrically */
 		if (sjinfo->jointype == JOIN_FULL &&
 			bms_overlap(joinrel->relids, sjinfo->min_lefthand) &&
 			!bms_overlap(joinrel->relids, sjinfo->min_righthand))
-			param_source_rels = bms_join(param_source_rels,
-										 bms_difference(root->all_baserels,
+			extra.param_source_rels = bms_join(extra.param_source_rels,
+										   bms_difference(root->all_baserels,
 													  sjinfo->min_lefthand));
 	}
 
@@ -168,9 +163,9 @@ add_paths_to_joinrel(PlannerInfo *root,
 		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(lc);
 
 		if (bms_is_subset(ljinfo->lateral_rhs, joinrel->relids))
-			param_source_rels = bms_join(param_source_rels,
-										 bms_difference(ljinfo->lateral_lhs,
-														joinrel->relids));
+			extra.param_source_rels = bms_join(extra.param_source_rels,
+										  bms_difference(ljinfo->lateral_lhs,
+														 joinrel->relids));
 	}
 
 	/*
@@ -195,8 +190,8 @@ add_paths_to_joinrel(PlannerInfo *root,
 			!bms_is_subset(phinfo->ph_eval_at, innerrel->relids))
 		{
 			/* Yes, remember its lateral rels */
-			extra_lateral_rels = bms_add_members(extra_lateral_rels,
-												 phinfo->ph_lateral);
+			extra.extra_lateral_rels = bms_add_members(extra.extra_lateral_rels,
+													   phinfo->ph_lateral);
 		}
 	}
 
@@ -206,9 +201,10 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 * it to required_outer below, while preserving the property that
 	 * required_outer is exactly NULL if empty.)
 	 */
-	extra_lateral_rels = bms_del_members(extra_lateral_rels, joinrel->relids);
-	if (bms_is_empty(extra_lateral_rels))
-		extra_lateral_rels = NULL;
+	extra.extra_lateral_rels = bms_del_members(extra.extra_lateral_rels,
+											   joinrel->relids);
+	if (bms_is_empty(extra.extra_lateral_rels))
+		extra.extra_lateral_rels = NULL;
 
 	/*
 	 * 1. Consider mergejoin paths where both relations must be explicitly
@@ -216,9 +212,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 */
 	if (mergejoin_allowed)
 		sort_inner_and_outer(root, joinrel, outerrel, innerrel,
-							 restrictlist, mergeclause_list, jointype,
-							 sjinfo,
-							 param_source_rels, extra_lateral_rels);
+							 jointype, &extra);
 
 	/*
 	 * 2. Consider paths where the outer relation need not be explicitly
@@ -229,9 +223,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 */
 	if (mergejoin_allowed)
 		match_unsorted_outer(root, joinrel, outerrel, innerrel,
-							 restrictlist, mergeclause_list, jointype,
-							 sjinfo, &semifactors,
-							 param_source_rels, extra_lateral_rels);
+							 jointype, &extra);
 
 #ifdef NOT_USED
 
@@ -248,9 +240,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 */
 	if (mergejoin_allowed)
 		match_unsorted_inner(root, joinrel, outerrel, innerrel,
-							 restrictlist, mergeclause_list, jointype,
-							 sjinfo, &semifactors,
-							 param_source_rels, extra_lateral_rels);
+							 jointype, &extra);
 #endif
 
 	/*
@@ -260,30 +250,24 @@ add_paths_to_joinrel(PlannerInfo *root,
 	 */
 	if (enable_hashjoin || jointype == JOIN_FULL)
 		hash_inner_and_outer(root, joinrel, outerrel, innerrel,
-							 restrictlist, jointype,
-							 sjinfo, &semifactors,
-							 param_source_rels, extra_lateral_rels);
+							 jointype, &extra);
 
 	/*
-	 * 5. If both inner and outer relations are managed by the same FDW,
-	 * give it a chance to push down joins.
+	 * 5. If inner and outer relations are foreign tables (or joins) belonging
+	 * to the same server, give the FDW a chance to push down joins.
 	 */
 	if (joinrel->fdwroutine &&
 		joinrel->fdwroutine->GetForeignJoinPaths)
 		joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel,
 												 outerrel, innerrel,
-												 restrictlist, jointype, sjinfo,
-												 &semifactors,
-												 param_source_rels,
-												 extra_lateral_rels);
+												 jointype, &extra);
+
 	/*
 	 * 6. Finally, give extensions a chance to manipulate the path list.
 	 */
 	if (set_join_pathlist_hook)
 		set_join_pathlist_hook(root, joinrel, outerrel, innerrel,
-							   restrictlist, jointype,
-							   sjinfo, &semifactors,
-							   param_source_rels, extra_lateral_rels);
+							   jointype, &extra);
 }
 
 /*
@@ -294,15 +278,11 @@ add_paths_to_joinrel(PlannerInfo *root,
 static void
 try_nestloop_path(PlannerInfo *root,
 				  RelOptInfo *joinrel,
-				  JoinType jointype,
-				  SpecialJoinInfo *sjinfo,
-				  SemiAntiJoinFactors *semifactors,
-				  Relids param_source_rels,
-				  Relids extra_lateral_rels,
 				  Path *outer_path,
 				  Path *inner_path,
-				  List *restrict_clauses,
-				  List *pathkeys)
+				  List *pathkeys,
+				  JoinType jointype,
+				  JoinPathExtraData *extra)
 {
 	Relids		required_outer;
 	JoinCostWorkspace workspace;
@@ -314,7 +294,7 @@ try_nestloop_path(PlannerInfo *root,
 	required_outer = calc_nestloop_required_outer(outer_path,
 												  inner_path);
 	if (required_outer &&
-		!bms_overlap(required_outer, param_source_rels))
+		!bms_overlap(required_outer, extra->param_source_rels))
 	{
 		/*
 		 * We override the param_source_rels heuristic to accept nestloop
@@ -345,7 +325,7 @@ try_nestloop_path(PlannerInfo *root,
 	 * Independently of that, add parameterization needed for any
 	 * PlaceHolderVars that need to be computed at the join.
 	 */
-	required_outer = bms_add_members(required_outer, extra_lateral_rels);
+	required_outer = bms_add_members(required_outer, extra->extra_lateral_rels);
 
 	/*
 	 * Do a precheck to quickly eliminate obviously-inferior paths.  We
@@ -358,7 +338,7 @@ try_nestloop_path(PlannerInfo *root,
 	 */
 	initial_cost_nestloop(root, &workspace, jointype,
 						  outer_path, inner_path,
-						  sjinfo, semifactors);
+						  extra->sjinfo, &extra->semifactors);
 
 	if (add_path_precheck(joinrel,
 						  workspace.startup_cost, workspace.total_cost,
@@ -369,11 +349,11 @@ try_nestloop_path(PlannerInfo *root,
 									  joinrel,
 									  jointype,
 									  &workspace,
-									  sjinfo,
-									  semifactors,
+									  extra->sjinfo,
+									  &extra->semifactors,
 									  outer_path,
 									  inner_path,
-									  restrict_clauses,
+									  extra->restrictlist,
 									  pathkeys,
 									  required_outer));
 	}
@@ -392,17 +372,14 @@ try_nestloop_path(PlannerInfo *root,
 static void
 try_mergejoin_path(PlannerInfo *root,
 				   RelOptInfo *joinrel,
-				   JoinType jointype,
-				   SpecialJoinInfo *sjinfo,
-				   Relids param_source_rels,
-				   Relids extra_lateral_rels,
 				   Path *outer_path,
 				   Path *inner_path,
-				   List *restrict_clauses,
 				   List *pathkeys,
 				   List *mergeclauses,
 				   List *outersortkeys,
-				   List *innersortkeys)
+				   List *innersortkeys,
+				   JoinType jointype,
+				   JoinPathExtraData *extra)
 {
 	Relids		required_outer;
 	JoinCostWorkspace workspace;
@@ -414,7 +391,7 @@ try_mergejoin_path(PlannerInfo *root,
 	required_outer = calc_non_nestloop_required_outer(outer_path,
 													  inner_path);
 	if (required_outer &&
-		!bms_overlap(required_outer, param_source_rels))
+		!bms_overlap(required_outer, extra->param_source_rels))
 	{
 		/* Waste no memory when we reject a path here */
 		bms_free(required_outer);
@@ -425,7 +402,7 @@ try_mergejoin_path(PlannerInfo *root,
 	 * Independently of that, add parameterization needed for any
 	 * PlaceHolderVars that need to be computed at the join.
 	 */
-	required_outer = bms_add_members(required_outer, extra_lateral_rels);
+	required_outer = bms_add_members(required_outer, extra->extra_lateral_rels);
 
 	/*
 	 * If the given paths are already well enough ordered, we can skip doing
@@ -444,7 +421,7 @@ try_mergejoin_path(PlannerInfo *root,
 	initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
 						   outer_path, inner_path,
 						   outersortkeys, innersortkeys,
-						   sjinfo);
+						   extra->sjinfo);
 
 	if (add_path_precheck(joinrel,
 						  workspace.startup_cost, workspace.total_cost,
@@ -455,10 +432,10 @@ try_mergejoin_path(PlannerInfo *root,
 									   joinrel,
 									   jointype,
 									   &workspace,
-									   sjinfo,
+									   extra->sjinfo,
 									   outer_path,
 									   inner_path,
-									   restrict_clauses,
+									   extra->restrictlist,
 									   pathkeys,
 									   required_outer,
 									   mergeclauses,
@@ -480,15 +457,11 @@ try_mergejoin_path(PlannerInfo *root,
 static void
 try_hashjoin_path(PlannerInfo *root,
 				  RelOptInfo *joinrel,
-				  JoinType jointype,
-				  SpecialJoinInfo *sjinfo,
-				  SemiAntiJoinFactors *semifactors,
-				  Relids param_source_rels,
-				  Relids extra_lateral_rels,
 				  Path *outer_path,
 				  Path *inner_path,
-				  List *restrict_clauses,
-				  List *hashclauses)
+				  List *hashclauses,
+				  JoinType jointype,
+				  JoinPathExtraData *extra)
 {
 	Relids		required_outer;
 	JoinCostWorkspace workspace;
@@ -500,7 +473,7 @@ try_hashjoin_path(PlannerInfo *root,
 	required_outer = calc_non_nestloop_required_outer(outer_path,
 													  inner_path);
 	if (required_outer &&
-		!bms_overlap(required_outer, param_source_rels))
+		!bms_overlap(required_outer, extra->param_source_rels))
 	{
 		/* Waste no memory when we reject a path here */
 		bms_free(required_outer);
@@ -511,7 +484,7 @@ try_hashjoin_path(PlannerInfo *root,
 	 * Independently of that, add parameterization needed for any
 	 * PlaceHolderVars that need to be computed at the join.
 	 */
-	required_outer = bms_add_members(required_outer, extra_lateral_rels);
+	required_outer = bms_add_members(required_outer, extra->extra_lateral_rels);
 
 	/*
 	 * See comments in try_nestloop_path().  Also note that hashjoin paths
@@ -519,7 +492,7 @@ try_hashjoin_path(PlannerInfo *root,
 	 */
 	initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
 						  outer_path, inner_path,
-						  sjinfo, semifactors);
+						  extra->sjinfo, &extra->semifactors);
 
 	if (add_path_precheck(joinrel,
 						  workspace.startup_cost, workspace.total_cost,
@@ -530,11 +503,11 @@ try_hashjoin_path(PlannerInfo *root,
 									  joinrel,
 									  jointype,
 									  &workspace,
-									  sjinfo,
-									  semifactors,
+									  extra->sjinfo,
+									  &extra->semifactors,
 									  outer_path,
 									  inner_path,
-									  restrict_clauses,
+									  extra->restrictlist,
 									  required_outer,
 									  hashclauses));
 	}
@@ -584,26 +557,16 @@ clause_sides_match_join(RestrictInfo *rinfo, RelOptInfo *outerrel,
  * 'joinrel' is the join relation
  * 'outerrel' is the outer join relation
  * 'innerrel' is the inner join relation
- * 'restrictlist' contains all of the RestrictInfo nodes for restriction
- *		clauses that apply to this join
- * 'mergeclause_list' is a list of RestrictInfo nodes for available
- *		mergejoin clauses in this join
  * 'jointype' is the type of join to do
- * 'sjinfo' is extra info about the join for selectivity estimation
- * 'param_source_rels' are OK targets for parameterization of result paths
- * 'extra_lateral_rels' are additional parameterization for result paths
+ * 'extra' contains additional input values
  */
 static void
 sort_inner_and_outer(PlannerInfo *root,
 					 RelOptInfo *joinrel,
 					 RelOptInfo *outerrel,
 					 RelOptInfo *innerrel,
-					 List *restrictlist,
-					 List *mergeclause_list,
 					 JoinType jointype,
-					 SpecialJoinInfo *sjinfo,
-					 Relids param_source_rels,
-					 Relids extra_lateral_rels)
+					 JoinPathExtraData *extra)
 {
 	Path	   *outer_path;
 	Path	   *inner_path;
@@ -643,14 +606,14 @@ sort_inner_and_outer(PlannerInfo *root,
 	if (jointype == JOIN_UNIQUE_OUTER)
 	{
 		outer_path = (Path *) create_unique_path(root, outerrel,
-												 outer_path, sjinfo);
+												 outer_path, extra->sjinfo);
 		Assert(outer_path);
 		jointype = JOIN_INNER;
 	}
 	else if (jointype == JOIN_UNIQUE_INNER)
 	{
 		inner_path = (Path *) create_unique_path(root, innerrel,
-												 inner_path, sjinfo);
+												 inner_path, extra->sjinfo);
 		Assert(inner_path);
 		jointype = JOIN_INNER;
 	}
@@ -684,7 +647,7 @@ sort_inner_and_outer(PlannerInfo *root,
 	 * exactly as-is as well as making variants.
 	 */
 	all_pathkeys = select_outer_pathkeys_for_merge(root,
-												   mergeclause_list,
+												   extra->mergeclause_list,
 												   joinrel);
 
 	foreach(l, all_pathkeys)
@@ -707,10 +670,10 @@ sort_inner_and_outer(PlannerInfo *root,
 		cur_mergeclauses = find_mergeclauses_for_pathkeys(root,
 														  outerkeys,
 														  true,
-														  mergeclause_list);
+													extra->mergeclause_list);
 
 		/* Should have used them all... */
-		Assert(list_length(cur_mergeclauses) == list_length(mergeclause_list));
+		Assert(list_length(cur_mergeclauses) == list_length(extra->mergeclause_list));
 
 		/* Build sort pathkeys for the inner side */
 		innerkeys = make_inner_pathkeys_for_merge(root,
@@ -730,17 +693,14 @@ sort_inner_and_outer(PlannerInfo *root,
 		 */
 		try_mergejoin_path(root,
 						   joinrel,
-						   jointype,
-						   sjinfo,
-						   param_source_rels,
-						   extra_lateral_rels,
 						   outer_path,
 						   inner_path,
-						   restrictlist,
 						   merge_pathkeys,
 						   cur_mergeclauses,
 						   outerkeys,
-						   innerkeys);
+						   innerkeys,
+						   jointype,
+						   extra);
 	}
 }
 
@@ -771,28 +731,16 @@ sort_inner_and_outer(PlannerInfo *root,
  * 'joinrel' is the join relation
  * 'outerrel' is the outer join relation
  * 'innerrel' is the inner join relation
- * 'restrictlist' contains all of the RestrictInfo nodes for restriction
- *		clauses that apply to this join
- * 'mergeclause_list' is a list of RestrictInfo nodes for available
- *		mergejoin clauses in this join
  * 'jointype' is the type of join to do
- * 'sjinfo' is extra info about the join for selectivity estimation
- * 'semifactors' contains valid data if jointype is SEMI or ANTI
- * 'param_source_rels' are OK targets for parameterization of result paths
- * 'extra_lateral_rels' are additional parameterization for result paths
+ * 'extra' contains additional input values
  */
 static void
 match_unsorted_outer(PlannerInfo *root,
 					 RelOptInfo *joinrel,
 					 RelOptInfo *outerrel,
 					 RelOptInfo *innerrel,
-					 List *restrictlist,
-					 List *mergeclause_list,
 					 JoinType jointype,
-					 SpecialJoinInfo *sjinfo,
-					 SemiAntiJoinFactors *semifactors,
-					 Relids param_source_rels,
-					 Relids extra_lateral_rels)
+					 JoinPathExtraData *extra)
 {
 	JoinType	save_jointype = jointype;
 	bool		nestjoinOK;
@@ -854,7 +802,7 @@ match_unsorted_outer(PlannerInfo *root,
 		if (inner_cheapest_total == NULL)
 			return;
 		inner_cheapest_total = (Path *)
-			create_unique_path(root, innerrel, inner_cheapest_total, sjinfo);
+			create_unique_path(root, innerrel, inner_cheapest_total, extra->sjinfo);
 		Assert(inner_cheapest_total);
 	}
 	else if (nestjoinOK)
@@ -898,7 +846,7 @@ match_unsorted_outer(PlannerInfo *root,
 			if (outerpath != outerrel->cheapest_total_path)
 				continue;
 			outerpath = (Path *) create_unique_path(root, outerrel,
-													outerpath, sjinfo);
+													outerpath, extra->sjinfo);
 			Assert(outerpath);
 		}
 
@@ -918,15 +866,11 @@ match_unsorted_outer(PlannerInfo *root,
 			 */
 			try_nestloop_path(root,
 							  joinrel,
-							  jointype,
-							  sjinfo,
-							  semifactors,
-							  param_source_rels,
-							  extra_lateral_rels,
 							  outerpath,
 							  inner_cheapest_total,
-							  restrictlist,
-							  merge_pathkeys);
+							  merge_pathkeys,
+							  jointype,
+							  extra);
 		}
 		else if (nestjoinOK)
 		{
@@ -944,30 +888,22 @@ match_unsorted_outer(PlannerInfo *root,
 
 				try_nestloop_path(root,
 								  joinrel,
-								  jointype,
-								  sjinfo,
-								  semifactors,
-								  param_source_rels,
-								  extra_lateral_rels,
 								  outerpath,
 								  innerpath,
-								  restrictlist,
-								  merge_pathkeys);
+								  merge_pathkeys,
+								  jointype,
+								  extra);
 			}
 
 			/* Also consider materialized form of the cheapest inner path */
 			if (matpath != NULL)
 				try_nestloop_path(root,
 								  joinrel,
-								  jointype,
-								  sjinfo,
-								  semifactors,
-								  param_source_rels,
-								  extra_lateral_rels,
 								  outerpath,
 								  matpath,
-								  restrictlist,
-								  merge_pathkeys);
+								  merge_pathkeys,
+								  jointype,
+								  extra);
 		}
 
 		/* Can't do anything else if outer path needs to be unique'd */
@@ -982,7 +918,7 @@ match_unsorted_outer(PlannerInfo *root,
 		mergeclauses = find_mergeclauses_for_pathkeys(root,
 													  outerpath->pathkeys,
 													  true,
-													  mergeclause_list);
+													extra->mergeclause_list);
 
 		/*
 		 * Done with this outer path if no chance for a mergejoin.
@@ -1000,7 +936,7 @@ match_unsorted_outer(PlannerInfo *root,
 			else
 				continue;
 		}
-		if (useallclauses && list_length(mergeclauses) != list_length(mergeclause_list))
+		if (useallclauses && list_length(mergeclauses) != list_length(extra->mergeclause_list))
 			continue;
 
 		/* Compute the required ordering of the inner path */
@@ -1016,17 +952,14 @@ match_unsorted_outer(PlannerInfo *root,
 		 */
 		try_mergejoin_path(root,
 						   joinrel,
-						   jointype,
-						   sjinfo,
-						   param_source_rels,
-						   extra_lateral_rels,
 						   outerpath,
 						   inner_cheapest_total,
-						   restrictlist,
 						   merge_pathkeys,
 						   mergeclauses,
 						   NIL,
-						   innersortkeys);
+						   innersortkeys,
+						   jointype,
+						   extra);
 
 		/* Can't do anything else if inner path needs to be unique'd */
 		if (save_jointype == JOIN_UNIQUE_INNER)
@@ -1115,17 +1048,14 @@ match_unsorted_outer(PlannerInfo *root,
 					newclauses = mergeclauses;
 				try_mergejoin_path(root,
 								   joinrel,
-								   jointype,
-								   sjinfo,
-								   param_source_rels,
-								   extra_lateral_rels,
 								   outerpath,
 								   innerpath,
-								   restrictlist,
 								   merge_pathkeys,
 								   newclauses,
 								   NIL,
-								   NIL);
+								   NIL,
+								   jointype,
+								   extra);
 				cheapest_total_inner = innerpath;
 			}
 			/* Same on the basis of cheapest startup cost ... */
@@ -1161,17 +1091,14 @@ match_unsorted_outer(PlannerInfo *root,
 					}
 					try_mergejoin_path(root,
 									   joinrel,
-									   jointype,
-									   sjinfo,
-									   param_source_rels,
-									   extra_lateral_rels,
 									   outerpath,
 									   innerpath,
-									   restrictlist,
 									   merge_pathkeys,
 									   newclauses,
 									   NIL,
-									   NIL);
+									   NIL,
+									   jointype,
+									   extra);
 				}
 				cheapest_startup_inner = innerpath;
 			}
@@ -1193,25 +1120,16 @@ match_unsorted_outer(PlannerInfo *root,
  * 'joinrel' is the join relation
  * 'outerrel' is the outer join relation
  * 'innerrel' is the inner join relation
- * 'restrictlist' contains all of the RestrictInfo nodes for restriction
- *		clauses that apply to this join
  * 'jointype' is the type of join to do
- * 'sjinfo' is extra info about the join for selectivity estimation
- * 'semifactors' contains valid data if jointype is SEMI or ANTI
- * 'param_source_rels' are OK targets for parameterization of result paths
- * 'extra_lateral_rels' are additional parameterization for result paths
+ * 'extra' contains additional input values
  */
 static void
 hash_inner_and_outer(PlannerInfo *root,
 					 RelOptInfo *joinrel,
 					 RelOptInfo *outerrel,
 					 RelOptInfo *innerrel,
-					 List *restrictlist,
 					 JoinType jointype,
-					 SpecialJoinInfo *sjinfo,
-					 SemiAntiJoinFactors *semifactors,
-					 Relids param_source_rels,
-					 Relids extra_lateral_rels)
+					 JoinPathExtraData *extra)
 {
 	bool		isouterjoin = IS_OUTER_JOIN(jointype);
 	List	   *hashclauses;
@@ -1225,7 +1143,7 @@ hash_inner_and_outer(PlannerInfo *root,
 	 * usable with this pair of sub-relations.
 	 */
 	hashclauses = NIL;
-	foreach(l, restrictlist)
+	foreach(l, extra->restrictlist)
 	{
 		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
 
@@ -1276,53 +1194,41 @@ hash_inner_and_outer(PlannerInfo *root,
 		{
 			cheapest_total_outer = (Path *)
 				create_unique_path(root, outerrel,
-								   cheapest_total_outer, sjinfo);
+								   cheapest_total_outer, extra->sjinfo);
 			Assert(cheapest_total_outer);
 			jointype = JOIN_INNER;
 			try_hashjoin_path(root,
 							  joinrel,
-							  jointype,
-							  sjinfo,
-							  semifactors,
-							  param_source_rels,
-							  extra_lateral_rels,
 							  cheapest_total_outer,
 							  cheapest_total_inner,
-							  restrictlist,
-							  hashclauses);
+							  hashclauses,
+							  jointype,
+							  extra);
 			/* no possibility of cheap startup here */
 		}
 		else if (jointype == JOIN_UNIQUE_INNER)
 		{
 			cheapest_total_inner = (Path *)
 				create_unique_path(root, innerrel,
-								   cheapest_total_inner, sjinfo);
+								   cheapest_total_inner, extra->sjinfo);
 			Assert(cheapest_total_inner);
 			jointype = JOIN_INNER;
 			try_hashjoin_path(root,
 							  joinrel,
-							  jointype,
-							  sjinfo,
-							  semifactors,
-							  param_source_rels,
-							  extra_lateral_rels,
 							  cheapest_total_outer,
 							  cheapest_total_inner,
-							  restrictlist,
-							  hashclauses);
+							  hashclauses,
+							  jointype,
+							  extra);
 			if (cheapest_startup_outer != NULL &&
 				cheapest_startup_outer != cheapest_total_outer)
 				try_hashjoin_path(root,
 								  joinrel,
-								  jointype,
-								  sjinfo,
-								  semifactors,
-								  param_source_rels,
-								  extra_lateral_rels,
 								  cheapest_startup_outer,
 								  cheapest_total_inner,
-								  restrictlist,
-								  hashclauses);
+								  hashclauses,
+								  jointype,
+								  extra);
 		}
 		else
 		{
@@ -1339,15 +1245,11 @@ hash_inner_and_outer(PlannerInfo *root,
 			if (cheapest_startup_outer != NULL)
 				try_hashjoin_path(root,
 								  joinrel,
-								  jointype,
-								  sjinfo,
-								  semifactors,
-								  param_source_rels,
-								  extra_lateral_rels,
 								  cheapest_startup_outer,
 								  cheapest_total_inner,
-								  restrictlist,
-								  hashclauses);
+								  hashclauses,
+								  jointype,
+								  extra);
 
 			foreach(lc1, outerrel->cheapest_parameterized_paths)
 			{
@@ -1377,15 +1279,11 @@ hash_inner_and_outer(PlannerInfo *root,
 
 					try_hashjoin_path(root,
 									  joinrel,
-									  jointype,
-									  sjinfo,
-									  semifactors,
-									  param_source_rels,
-									  extra_lateral_rels,
 									  outerpath,
 									  innerpath,
-									  restrictlist,
-									  hashclauses);
+									  hashclauses,
+									  jointype,
+									  extra);
 				}
 			}
 		}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 3246332d6e3bda4cb5aab2fdce2a6b823722f84d..c809237283198ad4d29e6ddce1acd057371cf6bb 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -44,6 +44,7 @@
 #include "utils/lsyscache.h"
 
 
+static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
 static Plan *create_scan_plan(PlannerInfo *root, Path *best_path);
 static List *build_path_tlist(PlannerInfo *root, Path *path);
 static bool use_physical_tlist(PlannerInfo *root, RelOptInfo *rel);
@@ -219,7 +220,7 @@ create_plan(PlannerInfo *root, Path *best_path)
  * create_plan_recurse
  *	  Recursive guts of create_plan().
  */
-Plan *
+static Plan *
 create_plan_recurse(PlannerInfo *root, Path *best_path)
 {
 	Plan	   *plan;
@@ -1950,7 +1951,7 @@ create_worktablescan_plan(PlannerInfo *root, Path *best_path,
 
 /*
  * create_foreignscan_plan
- *	 Returns a foreignscan plan for the base relation scanned by 'best_path'
+ *	 Returns a foreignscan plan for the relation scanned by 'best_path'
  *	 with restriction clauses 'scan_clauses' and targetlist 'tlist'.
  */
 static ForeignScan *
@@ -1965,9 +1966,11 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	ListCell   *lc;
 	int			i;
 
+	Assert(rel->fdwroutine != NULL);
+
 	/*
-	 * If we're scanning a base relation, look up the OID.
-	 * (We can skip this if scanning a join relation.)
+	 * If we're scanning a base relation, fetch its OID.  (Irrelevant if
+	 * scanning a join relation.)
 	 */
 	if (scan_relid > 0)
 	{
@@ -1978,7 +1981,6 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 		Assert(rte->rtekind == RTE_RELATION);
 		rel_oid = rte->relid;
 	}
-	Assert(rel->fdwroutine != NULL);
 
 	/*
 	 * Sort clauses into best execution order.  We do this first since the FDW
@@ -1996,42 +1998,22 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	scan_plan = rel->fdwroutine->GetForeignPlan(root, rel, rel_oid,
 												best_path,
 												tlist, scan_clauses);
-	/*
-	 * Sanity check.  There may be resjunk entries in fdw_ps_tlist that
-	 * are included only to help EXPLAIN deparse plans properly. We require
-	 * that these are at the end, so that when the executor builds the scan
-	 * descriptor based on the non-junk entries, it gets the attribute
-	 * numbers correct.
-	 */
-	if (scan_plan->scan.scanrelid == 0)
-	{
-		bool	found_resjunk = false;
-
-		foreach (lc, scan_plan->fdw_ps_tlist)
-		{
-			TargetEntry	   *tle = lfirst(lc);
-
-			if (tle->resjunk)
-				found_resjunk = true;
-			else if (found_resjunk)
-				elog(ERROR, "junk TLE should not apper prior to valid one");
-		}
-	}
-	/* Set the relids that are represented by this foreign scan for Explain */
-	scan_plan->fdw_relids = best_path->path.parent->relids;
 
 	/* Copy cost data from Path to Plan; no need to make FDW do this */
 	copy_path_costsize(&scan_plan->scan.plan, &best_path->path);
 
-	/* Track FDW server-id; no need to make FDW do this */
-	scan_plan->fdw_handler = rel->fdw_handler;
+	/* Copy foreign server OID; likewise, no need to make FDW do this */
+	scan_plan->fs_server = rel->serverid;
+
+	/* Likewise, copy the relids that are represented by this foreign scan */
+	scan_plan->fs_relids = best_path->path.parent->relids;
 
 	/*
 	 * 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.)
+	 * wouldn't work.)  We assume fdw_scan_tlist contains no such variables.
 	 */
 	if (best_path->path.param_info)
 	{
@@ -2087,7 +2069,6 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
 {
 	CustomScan *cplan;
 	RelOptInfo *rel = best_path->path.parent;
-	ListCell   *lc;
 
 	/*
 	 * Sort clauses into the best execution order, although custom-scan
@@ -2106,42 +2087,22 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
 															  scan_clauses);
 	Assert(IsA(cplan, CustomScan));
 
-	/*
-	 * Sanity check.  There may be resjunk entries in custom_ps_tlist that
-	 * are included only to help EXPLAIN deparse plans properly. We require
-	 * that these are at the end, so that when the executor builds the scan
-	 * descriptor based on the non-junk entries, it gets the attribute
-	 * numbers correct.
-	 */
-	if (cplan->scan.scanrelid == 0)
-	{
-		bool	found_resjunk = false;
-
-		foreach (lc, cplan->custom_ps_tlist)
-		{
-			TargetEntry	   *tle = lfirst(lc);
-
-			if (tle->resjunk)
-				found_resjunk = true;
-			else if (found_resjunk)
-				elog(ERROR, "junk TLE should not apper prior to valid one");
-		}
-	}
-	/* Set the relids that are represented by this custom scan for Explain */
-	cplan->custom_relids = best_path->path.parent->relids;
-
 	/*
 	 * Copy cost data from Path to Plan; no need to make custom-plan providers
 	 * do this
 	 */
 	copy_path_costsize(&cplan->scan.plan, &best_path->path);
 
+	/* Likewise, copy the relids that are represented by this custom scan */
+	cplan->custom_relids = best_path->path.parent->relids;
+
 	/*
 	 * Replace any outer-relation variables with nestloop params in the qual
 	 * and custom_exprs expressions.  We do this last so that the custom-plan
 	 * provider doesn't have to be involved.  (Note that parts of custom_exprs
 	 * could have come from join clauses, so doing this beforehand on the
-	 * scan_clauses wouldn't work.)
+	 * scan_clauses wouldn't work.)  We assume custom_scan_tlist contains no
+	 * such variables.
 	 */
 	if (best_path->path.param_info)
 	{
@@ -3611,7 +3572,8 @@ make_foreignscan(List *qptlist,
 				 List *qpqual,
 				 Index scanrelid,
 				 List *fdw_exprs,
-				 List *fdw_private)
+				 List *fdw_private,
+				 List *fdw_scan_tlist)
 {
 	ForeignScan *node = makeNode(ForeignScan);
 	Plan	   *plan = &node->scan.plan;
@@ -3622,8 +3584,13 @@ make_foreignscan(List *qptlist,
 	plan->lefttree = NULL;
 	plan->righttree = NULL;
 	node->scan.scanrelid = scanrelid;
+	/* fs_server will be filled in by create_foreignscan_plan */
+	node->fs_server = InvalidOid;
 	node->fdw_exprs = fdw_exprs;
 	node->fdw_private = fdw_private;
+	node->fdw_scan_tlist = fdw_scan_tlist;
+	/* fs_relids will be filled in by create_foreignscan_plan */
+	node->fs_relids = NULL;
 	/* fsSystemCol will be filled in by create_foreignscan_plan */
 	node->fsSystemCol = false;
 
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 612d32571af390a61b6eb0561d5c557ea2770a38..fac51c9147470556bf23a063fc375e5f52f715ce 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -86,12 +86,6 @@ static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
 static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
 static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
 static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
-static void set_foreignscan_references(PlannerInfo *root,
-									   ForeignScan *fscan,
-									   int rtoffset);
-static void set_customscan_references(PlannerInfo *root,
-									  CustomScan *cscan,
-									  int rtoffset);
 static Plan *set_indexonlyscan_references(PlannerInfo *root,
 							 IndexOnlyScan *plan,
 							 int rtoffset);
@@ -99,6 +93,12 @@ static Plan *set_subqueryscan_references(PlannerInfo *root,
 							SubqueryScan *plan,
 							int rtoffset);
 static bool trivial_subqueryscan(SubqueryScan *plan);
+static void set_foreignscan_references(PlannerInfo *root,
+						   ForeignScan *fscan,
+						   int rtoffset);
+static void set_customscan_references(PlannerInfo *root,
+						  CustomScan *cscan,
+						  int rtoffset);
 static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
 static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
 static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
@@ -573,7 +573,6 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 		case T_ForeignScan:
 			set_foreignscan_references(root, (ForeignScan *) plan, rtoffset);
 			break;
-
 		case T_CustomScan:
 			set_customscan_references(root, (CustomScan *) plan, rtoffset);
 			break;
@@ -890,121 +889,6 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 	return plan;
 }
 
-/*
- * set_foreignscan_references
- *     Do set_plan_references processing on an ForeignScan
- */
-static void
-set_foreignscan_references(PlannerInfo *root,
-						   ForeignScan *fscan,
-						   int rtoffset)
-{
-	if (rtoffset > 0)
-	{
-		Bitmapset  *tempset = NULL;
-		int			x = -1;
-
-		while ((x = bms_next_member(fscan->fdw_relids, x)) >= 0)
-			tempset = bms_add_member(tempset, x + rtoffset);
-		fscan->fdw_relids = tempset;
-	}
-
-	if (fscan->scan.scanrelid == 0)
-	{
-		indexed_tlist *pscan_itlist = build_tlist_index(fscan->fdw_ps_tlist);
-
-		fscan->scan.plan.targetlist = (List *)
-			fix_upper_expr(root,
-						   (Node *) fscan->scan.plan.targetlist,
-						   pscan_itlist,
-						   INDEX_VAR,
-						   rtoffset);
-		fscan->scan.plan.qual = (List *)
-			fix_upper_expr(root,
-						   (Node *) fscan->scan.plan.qual,
-						   pscan_itlist,
-						   INDEX_VAR,
-						   rtoffset);
-		fscan->fdw_exprs = (List *)
-			fix_upper_expr(root,
-						   (Node *) fscan->fdw_exprs,
-						   pscan_itlist,
-						   INDEX_VAR,
-						   rtoffset);
-		fscan->fdw_ps_tlist =
-			fix_scan_list(root, fscan->fdw_ps_tlist, rtoffset);
-		pfree(pscan_itlist);
-	}
-	else
-	{
-		fscan->scan.scanrelid += rtoffset;
-		fscan->scan.plan.targetlist =
-			fix_scan_list(root, fscan->scan.plan.targetlist, rtoffset);
-		fscan->scan.plan.qual =
-			fix_scan_list(root, fscan->scan.plan.qual, rtoffset);
-		fscan->fdw_exprs =
-			fix_scan_list(root, fscan->fdw_exprs, rtoffset);
-	}
-}
-
-/*
- * set_customscan_references
- *     Do set_plan_references processing on an CustomScan
- */
-static void
-set_customscan_references(PlannerInfo *root,
-						  CustomScan *cscan,
-						  int rtoffset)
-{
-	if (rtoffset > 0)
-	{
-		Bitmapset  *tempset = NULL;
-		int			x = -1;
-
-		while ((x = bms_next_member(cscan->custom_relids, x)) >= 0)
-			tempset = bms_add_member(tempset, x + rtoffset);
-		cscan->custom_relids = tempset;
-	}
-
-	if (cscan->scan.scanrelid == 0)
-	{
-		indexed_tlist *pscan_itlist =
-			build_tlist_index(cscan->custom_ps_tlist);
-
-		cscan->scan.plan.targetlist = (List *)
-			fix_upper_expr(root,
-						   (Node *) cscan->scan.plan.targetlist,
-						   pscan_itlist,
-						   INDEX_VAR,
-						   rtoffset);
-		cscan->scan.plan.qual = (List *)
-			fix_upper_expr(root,
-						   (Node *) cscan->scan.plan.qual,
-						   pscan_itlist,
-						   INDEX_VAR,
-						   rtoffset);
-		cscan->custom_exprs = (List *)
-			fix_upper_expr(root,
-						   (Node *) cscan->custom_exprs,
-						   pscan_itlist,
-						   INDEX_VAR,
-						   rtoffset);
-		cscan->custom_ps_tlist =
-			fix_scan_list(root, cscan->custom_ps_tlist, rtoffset);
-		pfree(pscan_itlist);
-	}
-	else
-	{
-		cscan->scan.scanrelid += rtoffset;
-		cscan->scan.plan.targetlist =
-			fix_scan_list(root, cscan->scan.plan.targetlist, rtoffset);
-		cscan->scan.plan.qual =
-			fix_scan_list(root, cscan->scan.plan.qual, rtoffset);
-		cscan->custom_exprs =
-			fix_scan_list(root, cscan->custom_exprs, rtoffset);
-	}
-}
-
 /*
  * set_indexonlyscan_references
  *		Do set_plan_references processing on an IndexOnlyScan
@@ -1179,6 +1063,134 @@ trivial_subqueryscan(SubqueryScan *plan)
 	return true;
 }
 
+/*
+ * set_foreignscan_references
+ *	   Do set_plan_references processing on a ForeignScan
+ */
+static void
+set_foreignscan_references(PlannerInfo *root,
+						   ForeignScan *fscan,
+						   int rtoffset)
+{
+	/* Adjust scanrelid if it's valid */
+	if (fscan->scan.scanrelid > 0)
+		fscan->scan.scanrelid += rtoffset;
+
+	if (fscan->fdw_scan_tlist != NIL || fscan->scan.scanrelid == 0)
+	{
+		/* Adjust tlist, qual, fdw_exprs to reference custom scan tuple */
+		indexed_tlist *itlist = build_tlist_index(fscan->fdw_scan_tlist);
+
+		fscan->scan.plan.targetlist = (List *)
+			fix_upper_expr(root,
+						   (Node *) fscan->scan.plan.targetlist,
+						   itlist,
+						   INDEX_VAR,
+						   rtoffset);
+		fscan->scan.plan.qual = (List *)
+			fix_upper_expr(root,
+						   (Node *) fscan->scan.plan.qual,
+						   itlist,
+						   INDEX_VAR,
+						   rtoffset);
+		fscan->fdw_exprs = (List *)
+			fix_upper_expr(root,
+						   (Node *) fscan->fdw_exprs,
+						   itlist,
+						   INDEX_VAR,
+						   rtoffset);
+		pfree(itlist);
+		/* fdw_scan_tlist itself just needs fix_scan_list() adjustments */
+		fscan->fdw_scan_tlist =
+			fix_scan_list(root, fscan->fdw_scan_tlist, rtoffset);
+	}
+	else
+	{
+		/* Adjust tlist, qual, fdw_exprs in the standard way */
+		fscan->scan.plan.targetlist =
+			fix_scan_list(root, fscan->scan.plan.targetlist, rtoffset);
+		fscan->scan.plan.qual =
+			fix_scan_list(root, fscan->scan.plan.qual, rtoffset);
+		fscan->fdw_exprs =
+			fix_scan_list(root, fscan->fdw_exprs, rtoffset);
+	}
+
+	/* Adjust fs_relids if needed */
+	if (rtoffset > 0)
+	{
+		Bitmapset  *tempset = NULL;
+		int			x = -1;
+
+		while ((x = bms_next_member(fscan->fs_relids, x)) >= 0)
+			tempset = bms_add_member(tempset, x + rtoffset);
+		fscan->fs_relids = tempset;
+	}
+}
+
+/*
+ * set_customscan_references
+ *	   Do set_plan_references processing on a CustomScan
+ */
+static void
+set_customscan_references(PlannerInfo *root,
+						  CustomScan *cscan,
+						  int rtoffset)
+{
+	/* Adjust scanrelid if it's valid */
+	if (cscan->scan.scanrelid > 0)
+		cscan->scan.scanrelid += rtoffset;
+
+	if (cscan->custom_scan_tlist != NIL || cscan->scan.scanrelid == 0)
+	{
+		/* Adjust tlist, qual, custom_exprs to reference custom scan tuple */
+		indexed_tlist *itlist = build_tlist_index(cscan->custom_scan_tlist);
+
+		cscan->scan.plan.targetlist = (List *)
+			fix_upper_expr(root,
+						   (Node *) cscan->scan.plan.targetlist,
+						   itlist,
+						   INDEX_VAR,
+						   rtoffset);
+		cscan->scan.plan.qual = (List *)
+			fix_upper_expr(root,
+						   (Node *) cscan->scan.plan.qual,
+						   itlist,
+						   INDEX_VAR,
+						   rtoffset);
+		cscan->custom_exprs = (List *)
+			fix_upper_expr(root,
+						   (Node *) cscan->custom_exprs,
+						   itlist,
+						   INDEX_VAR,
+						   rtoffset);
+		pfree(itlist);
+		/* custom_scan_tlist itself just needs fix_scan_list() adjustments */
+		cscan->custom_scan_tlist =
+			fix_scan_list(root, cscan->custom_scan_tlist, rtoffset);
+	}
+	else
+	{
+		/* Adjust tlist, qual, custom_exprs in the standard way */
+		cscan->scan.plan.targetlist =
+			fix_scan_list(root, cscan->scan.plan.targetlist, rtoffset);
+		cscan->scan.plan.qual =
+			fix_scan_list(root, cscan->scan.plan.qual, rtoffset);
+		cscan->custom_exprs =
+			fix_scan_list(root, cscan->custom_exprs, rtoffset);
+	}
+
+	/* Adjust custom_relids if needed */
+	if (rtoffset > 0)
+	{
+		Bitmapset  *tempset = NULL;
+		int			x = -1;
+
+		while ((x = bms_next_member(cscan->custom_relids, x)) >= 0)
+			tempset = bms_add_member(tempset, x + rtoffset);
+		cscan->custom_relids = tempset;
+	}
+}
+
 /*
  * copyVar
  *		Copy a Var node.
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 0220672fc4382cad381ff564e474d3fb6bcf3798..afccee53acf562bf5006f7c63ed89ff6dd192ac5 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2318,12 +2318,14 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 		case T_ForeignScan:
 			finalize_primnode((Node *) ((ForeignScan *) plan)->fdw_exprs,
 							  &context);
+			/* We assume fdw_scan_tlist cannot contain Params */
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
 		case T_CustomScan:
 			finalize_primnode((Node *) ((CustomScan *) plan)->custom_exprs,
 							  &context);
+			/* We assume custom_scan_tlist cannot contain Params */
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 894e0db802de81e0eaa506c92aeeb6a6121d4e51..b425680f47647f19eaa89da02cfac8d3c8d9f765 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -380,17 +380,18 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 	rel->indexlist = indexinfos;
 
-	/* Grab the fdwroutine info using the relcache, while we have it */
+	/* Grab foreign-table info using the relcache, while we have it */
 	if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
 	{
-		rel->fdw_handler = GetFdwHandlerByRelId(RelationGetRelid(relation));
+		rel->serverid = GetForeignServerIdByRelId(RelationGetRelid(relation));
 		rel->fdwroutine = GetFdwRoutineForRelation(relation, true);
 	}
 	else
 	{
-		rel->fdw_handler = InvalidOid;
+		rel->serverid = InvalidOid;
 		rel->fdwroutine = NULL;
 	}
+
 	heap_close(relation, NoLock);
 
 	/*
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 56235663d7f99c6193f8ea128f15abbe4374eca2..1d635cd6d214bcd25b2fef6cab1c09e91e9b3dcb 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -14,7 +14,6 @@
  */
 #include "postgres.h"
 
-#include "foreign/fdwapi.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -122,8 +121,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	rel->subplan = NULL;
 	rel->subroot = NULL;
 	rel->subplan_params = NIL;
+	rel->serverid = InvalidOid;
 	rel->fdwroutine = NULL;
-	rel->fdw_handler = InvalidOid;
 	rel->fdw_private = NULL;
 	rel->baserestrictinfo = NIL;
 	rel->baserestrictcost.startup = 0;
@@ -385,6 +384,7 @@ build_join_rel(PlannerInfo *root,
 	joinrel->subplan = NULL;
 	joinrel->subroot = NULL;
 	joinrel->subplan_params = NIL;
+	joinrel->serverid = InvalidOid;
 	joinrel->fdwroutine = NULL;
 	joinrel->fdw_private = NULL;
 	joinrel->baserestrictinfo = NIL;
@@ -393,6 +393,17 @@ build_join_rel(PlannerInfo *root,
 	joinrel->joininfo = NIL;
 	joinrel->has_eclass_joins = false;
 
+	/*
+	 * Set up foreign-join fields if outer and inner relation are foreign
+	 * tables (or joins) belonging to the same server.
+	 */
+	if (OidIsValid(outer_rel->serverid) &&
+		inner_rel->serverid == outer_rel->serverid)
+	{
+		joinrel->serverid = outer_rel->serverid;
+		joinrel->fdwroutine = outer_rel->fdwroutine;
+	}
+
 	/*
 	 * Create a new tlist containing just the vars that need to be output from
 	 * this join (ie, are needed for higher joinclauses or final output).
@@ -428,18 +439,6 @@ build_join_rel(PlannerInfo *root,
 	set_joinrel_size_estimates(root, joinrel, outer_rel, inner_rel,
 							   sjinfo, restrictlist);
 
-	/*
-	 * Set FDW handler and routine if both outer and inner relation
-	 * are managed by same FDW driver.
-	 */
-	if (OidIsValid(outer_rel->fdw_handler) &&
-		OidIsValid(inner_rel->fdw_handler) &&
-		outer_rel->fdw_handler == inner_rel->fdw_handler)
-	{
-		joinrel->fdw_handler = outer_rel->fdw_handler;
-		joinrel->fdwroutine = GetFdwRoutine(joinrel->fdw_handler);
-	}
-
 	/*
 	 * Add the joinrel to the query's joinrel list, and store it into the
 	 * auxiliary hashtable if there is one.  NB: GEQO requires us to append
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 4b3cd85ad9043d6b6e5c7d49ef95ab8492102c8a..156b5331f36fb1965c3185770b9a23ddbc0028e7 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -128,8 +128,8 @@ typedef struct
  * varlevelsup > 0).  We store the PlanState node that is the immediate
  * parent of the expression to be deparsed, as well as a list of that
  * PlanState's ancestors.  In addition, we store its outer and inner subplan
- * state nodes, as well as their plan nodes' targetlists, and the indextlist
- * if the current PlanState is an IndexOnlyScanState.  (These fields could
+ * state nodes, as well as their plan nodes' targetlists, and the index tlist
+ * if the current plan node might contain INDEX_VAR Vars.  (These fields could
  * be derived on-the-fly from the current PlanState, but it seems notationally
  * clearer to set them up as separate fields.)
  */
@@ -2586,10 +2586,11 @@ deparse_context_for_plan_rtable(List *rtable, List *rtable_names)
  * provide the parent PlanState node.  Then OUTER_VAR and INNER_VAR references
  * can be resolved by drilling down into the left and right child plans.
  * Similarly, INDEX_VAR references can be resolved by reference to the
- * indextlist given in the parent IndexOnlyScan node.  (Note that we don't
- * currently support deparsing of indexquals in regular IndexScan or
- * BitmapIndexScan nodes; for those, we can only deparse the indexqualorig
- * fields, which won't contain INDEX_VAR Vars.)
+ * indextlist given in a parent IndexOnlyScan node, or to the scan tlist in
+ * ForeignScan and CustomScan nodes.  (Note that we don't currently support
+ * deparsing of indexquals in regular IndexScan or BitmapIndexScan nodes;
+ * for those, we can only deparse the indexqualorig fields, which won't
+ * contain INDEX_VAR Vars.)
  *
  * Note: planstate really ought to be declared as "PlanState *", but we use
  * "Node *" to avoid having to include execnodes.h in ruleutils.h.
@@ -3870,13 +3871,13 @@ set_deparse_planstate(deparse_namespace *dpns, PlanState *ps)
 	else
 		dpns->inner_tlist = NIL;
 
-	/* index_tlist is set only if it's an IndexOnlyScan */
+	/* Set up referent for INDEX_VAR Vars, if needed */
 	if (IsA(ps->plan, IndexOnlyScan))
 		dpns->index_tlist = ((IndexOnlyScan *) ps->plan)->indextlist;
 	else if (IsA(ps->plan, ForeignScan))
-		dpns->index_tlist = ((ForeignScan *) ps->plan)->fdw_ps_tlist;
+		dpns->index_tlist = ((ForeignScan *) ps->plan)->fdw_scan_tlist;
 	else if (IsA(ps->plan, CustomScan))
-		dpns->index_tlist = ((CustomScan *) ps->plan)->custom_ps_tlist;
+		dpns->index_tlist = ((CustomScan *) ps->plan)->custom_scan_tlist;
 	else
 		dpns->index_tlist = NIL;
 }
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 1b68b54c7ddff721a89ed0a321b360cef581f80b..6c646091976ce84629fd478d67162b64c3ba8c2f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -257,6 +257,7 @@ typedef bool (*ExecScanRecheckMtd) (ScanState *node, TupleTableSlot *slot);
 extern TupleTableSlot *ExecScan(ScanState *node, ExecScanAccessMtd accessMtd,
 		 ExecScanRecheckMtd recheckMtd);
 extern void ExecAssignScanProjectionInfo(ScanState *node);
+extern void ExecAssignScanProjectionInfoWithVarno(ScanState *node, Index varno);
 extern void ExecScanReScan(ScanState *node);
 
 /*
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index c683d9259e445aadd7e90145db693ca51bfad566..511c96b093275a697c7055c53cfeae3069905c13 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -47,6 +47,13 @@ typedef void (*ReScanForeignScan_function) (ForeignScanState *node);
 
 typedef void (*EndForeignScan_function) (ForeignScanState *node);
 
+typedef void (*GetForeignJoinPaths_function) (PlannerInfo *root,
+														  RelOptInfo *joinrel,
+														RelOptInfo *outerrel,
+														RelOptInfo *innerrel,
+														  JoinType jointype,
+												   JoinPathExtraData *extra);
+
 typedef void (*AddForeignUpdateTargets_function) (Query *parsetree,
 												   RangeTblEntry *target_rte,
 												   Relation target_relation);
@@ -82,17 +89,6 @@ typedef void (*EndForeignModify_function) (EState *estate,
 
 typedef int (*IsForeignRelUpdatable_function) (Relation rel);
 
-typedef void (*GetForeignJoinPaths_function) (PlannerInfo *root,
-											  RelOptInfo *joinrel,
-											  RelOptInfo *outerrel,
-											  RelOptInfo *innerrel,
-											  List *restrictlist,
-											  JoinType jointype,
-											  SpecialJoinInfo *sjinfo,
-											  SemiAntiJoinFactors *semifactors,
-											  Relids param_source_rels,
-											  Relids extra_lateral_rels);
-
 typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
 													struct ExplainState *es);
 
@@ -142,6 +138,9 @@ typedef struct FdwRoutine
 	 * are not provided.
 	 */
 
+	/* Functions for remote-join planning */
+	GetForeignJoinPaths_function GetForeignJoinPaths;
+
 	/* Functions for updating foreign tables */
 	AddForeignUpdateTargets_function AddForeignUpdateTargets;
 	PlanForeignModify_function PlanForeignModify;
@@ -161,15 +160,13 @@ typedef struct FdwRoutine
 
 	/* Support functions for IMPORT FOREIGN SCHEMA */
 	ImportForeignSchema_function ImportForeignSchema;
-
-	/* Support functions for join push-down */
-	GetForeignJoinPaths_function GetForeignJoinPaths;
 } FdwRoutine;
 
 
 /* Functions in foreign/foreign.c */
-extern Oid GetFdwHandlerByRelId(Oid relid);
 extern FdwRoutine *GetFdwRoutine(Oid fdwhandler);
+extern Oid	GetForeignServerIdByRelId(Oid relid);
+extern FdwRoutine *GetFdwRoutineByServerId(Oid serverid);
 extern FdwRoutine *GetFdwRoutineByRelId(Oid relid);
 extern FdwRoutine *GetFdwRoutineForRelation(Relation relation, bool makecopy);
 extern bool IsImportableForeignTable(const char *tablename,
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index c63492fa0be486a06354bbc641e857baa71e5974..9313292222afb97dd5e8b49f322073c5361d8476 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -479,32 +479,46 @@ typedef struct WorkTableScan
  * 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.
- * An optional fdw_ps_tlist is used to map a reference to an attribute of
- * underlying relation(s) onto a pair of INDEX_VAR and alternative varattno.
- * When fdw_ps_tlist is used, this represents a remote join, and the FDW
- * is responsible for setting this field to an appropriate value.
- * Note that everything in above lists must be copiable by copyObject().
+ * 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().
+ *
+ * fdw_scan_tlist is a targetlist describing the contents of the scan tuple
+ * returned by the FDW; it can be NIL if the scan tuple matches the declared
+ * rowtype of the foreign table, which is the normal case for a simple foreign
+ * table scan.  (If the plan node represents a foreign join, fdw_scan_tlist
+ * is required since there is no rowtype available from the system catalogs.)
+ * When fdw_scan_tlist is provided, Vars in the node's tlist and quals must
+ * have varno INDEX_VAR, and their varattnos correspond to resnos in the
+ * fdw_scan_tlist (which are also column numbers in the actual scan tuple).
+ * fdw_scan_tlist is never actually executed; it just holds expression trees
+ * describing what is in the scan tuple's columns.
+ *
+ * When the plan node represents a foreign join, scan.scanrelid is zero and
+ * fs_relids must be consulted to identify the join relation.  (fs_relids
+ * is valid for simple scans as well, but will always match scan.scanrelid.)
  * ----------------
  */
 typedef struct ForeignScan
 {
 	Scan		scan;
-	Oid			fdw_handler;	/* OID of FDW handler */
+	Oid			fs_server;		/* OID of foreign server */
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
-	List	   *fdw_ps_tlist;	/* tlist, if replacing a join */
 	List	   *fdw_private;	/* private data for FDW */
-	Bitmapset  *fdw_relids;		/* RTIs generated by this scan */
+	List	   *fdw_scan_tlist; /* optional tlist describing scan tuple */
+	Bitmapset  *fs_relids;		/* RTIs generated by this scan */
 	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
 /* ----------------
  *	   CustomScan node
  *
- * The comments for ForeignScan's fdw_exprs, fdw_varmap and fdw_private fields
- * apply equally to custom_exprs, custom_ps_tlist and custom_private.
+ * The comments for ForeignScan's fdw_exprs, fdw_private, fdw_scan_tlist,
+ * and fs_relids fields apply equally to CustomScan's custom_exprs,
+ * custom_private, custom_scan_tlist, and custom_relids fields.  The
+ * convention of setting scan.scanrelid to zero for joins applies as well.
+ *
  * Note that since Plan trees can be copied, custom scan providers *must*
  * fit all plan data they need into those fields; embedding CustomScan in
  * a larger struct will not work.
@@ -528,8 +542,9 @@ typedef struct CustomScan
 	Scan		scan;
 	uint32		flags;			/* mask of CUSTOMPATH_* flags, see relation.h */
 	List	   *custom_exprs;	/* expressions that custom code may evaluate */
-	List	   *custom_ps_tlist;/* tlist, if replacing a join */
 	List	   *custom_private; /* private data for custom code */
+	List	   *custom_scan_tlist;		/* optional tlist describing scan
+										 * tuple */
 	Bitmapset  *custom_relids;	/* RTIs generated by this scan */
 	const CustomScanMethods *methods;
 } CustomScan;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 8f2c64847e2f699f43825351a8fdee4a06e21a99..f10ae4efa881dc3a00acc659972ffa5f76d0f78f 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -127,9 +127,13 @@ typedef struct Expr
  * upper-level plan nodes are reassigned to point to the outputs of their
  * subplans; for example, in a join node varno becomes INNER_VAR or OUTER_VAR
  * and varattno becomes the index of the proper element of that subplan's
- * target list.  But varnoold/varoattno continue to hold the original values.
- * The code doesn't really need varnoold/varoattno, but they are very useful
- * for debugging and interpreting completed plans, so we keep them around.
+ * target list.  Similarly, INDEX_VAR is used to identify Vars that reference
+ * an index column rather than a heap column.  (In ForeignScan and CustomScan
+ * plan nodes, INDEX_VAR is abused to signify references to columns of a
+ * custom scan tuple type.)  In all these cases, varnoold/varoattno hold the
+ * original values.  The code doesn't really need varnoold/varoattno, but they
+ * are very useful for debugging and interpreting completed plans, so we keep
+ * them around.
  */
 #define    INNER_VAR		65000		/* reference to inner subplan */
 #define    OUTER_VAR		65001		/* reference to outer subplan */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 1713d298de256deb47c0bda3a8659de1283e0b66..d3ee61c4d046101b1a6dcbceb6821958ee72f939 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -365,18 +365,21 @@ typedef struct PlannerInfo
  *		subplan - plan for subquery (NULL if it's not a subquery)
  *		subroot - PlannerInfo for subquery (NULL if it's not a subquery)
  *		subplan_params - list of PlannerParamItems to be passed to subquery
- *		fdwroutine - function hooks for FDW, if foreign table (else NULL)
- *		fdw_handler - OID of FDW handler, if foreign table (else InvalidOid)
- *		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_subquery_pathlist processes the object.  Likewise, fdwroutine
- *		and fdw_private are filled during initial path creation.
+ *		set_subquery_pathlist processes the object.
  *
  *		For otherrels that are appendrel members, these fields are filled
  *		in just as for a baserel.
  *
+ * If the relation is either a foreign table or a join of foreign tables that
+ * all belong to the same foreign server, these fields will be set:
+ *
+ *		serverid - OID of foreign server, if foreign table (else InvalidOid)
+ *		fdwroutine - function hooks for FDW, if foreign table (else NULL)
+ *		fdw_private - private state for FDW, if foreign table (else NULL)
+ *
  * The presence of the remaining fields depends on the restrictions
  * and joins that the relation participates in:
  *
@@ -460,10 +463,12 @@ typedef struct RelOptInfo
 	struct Plan *subplan;		/* if subquery */
 	PlannerInfo *subroot;		/* if subquery */
 	List	   *subplan_params; /* if subquery */
+
+	/* Information about foreign tables and foreign joins */
+	Oid			serverid;		/* identifies server for the table or join */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
-	struct FdwRoutine *fdwroutine;		/* if foreign table */
-	Oid			fdw_handler;	/* if foreign table */
-	void	   *fdw_private;	/* if foreign table */
+	struct FdwRoutine *fdwroutine;
+	void	   *fdw_private;
 
 	/* used by various scans and joins: */
 	List	   *baserestrictinfo;		/* RestrictInfo structures (if base
@@ -523,7 +528,7 @@ typedef struct IndexOptInfo
 	bool	   *reverse_sort;	/* is sort order descending? */
 	bool	   *nulls_first;	/* do NULLs come first in the sort order? */
 	bool	   *canreturn;		/* which index cols can be returned in an
-								   index-only scan? */
+								 * index-only scan? */
 	Oid			relam;			/* OID of the access method (in pg_am) */
 
 	RegProcedure amcostestimate;	/* OID of the access method's cost fcn */
@@ -1667,6 +1672,28 @@ typedef struct SemiAntiJoinFactors
 	Selectivity match_count;
 } SemiAntiJoinFactors;
 
+/*
+ * Struct for extra information passed to subroutines of add_paths_to_joinrel
+ *
+ * restrictlist contains all of the RestrictInfo nodes for restriction
+ *		clauses that apply to this join
+ * mergeclause_list is a list of RestrictInfo nodes for available
+ *		mergejoin clauses in this join
+ * sjinfo is extra info about special joins for selectivity estimation
+ * semifactors is as shown above (only valid for SEMI or ANTI joins)
+ * param_source_rels are OK targets for parameterization of result paths
+ * extra_lateral_rels are additional parameterization for result paths
+ */
+typedef struct JoinPathExtraData
+{
+	List	   *restrictlist;
+	List	   *mergeclause_list;
+	SpecialJoinInfo *sjinfo;
+	SemiAntiJoinFactors semifactors;
+	Relids		param_source_rels;
+	Relids		extra_lateral_rels;
+} JoinPathExtraData;
+
 /*
  * For speed reasons, cost estimation for join paths is performed in two
  * phases: the first phase tries to quickly derive a lower bound for the
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index c42c69d7460751bb73edf96f015cc2f54fabfff2..3e2378aeb7df731fd600890190f1f9fc34ceb63d 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -32,15 +32,11 @@ extern PGDLLIMPORT set_rel_pathlist_hook_type set_rel_pathlist_hook;
 
 /* Hook for plugins to get control in add_paths_to_joinrel() */
 typedef void (*set_join_pathlist_hook_type) (PlannerInfo *root,
-											 RelOptInfo *joinrel,
-											 RelOptInfo *outerrel,
-											 RelOptInfo *innerrel,
-											 List *restrictlist,
-											 JoinType jointype,
-											 SpecialJoinInfo *sjinfo,
-											 SemiAntiJoinFactors *semifactors,
-											 Relids param_source_rels,
-											 Relids extra_lateral_rels);
+														 RelOptInfo *joinrel,
+														 RelOptInfo *outerrel,
+														 RelOptInfo *innerrel,
+														 JoinType jointype,
+												   JoinPathExtraData *extra);
 extern PGDLLIMPORT set_join_pathlist_hook_type set_join_pathlist_hook;
 
 /* Hook for plugins to replace standard_join_search() */
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 1d4ab0488e1e6e47de94fe1539c1f5d0c67b06e0..da15fca1f6c4374062ce8ffc8aaf28c46e416915 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -41,11 +41,11 @@ extern Plan *optimize_minmax_aggregates(PlannerInfo *root, List *tlist,
  * prototypes for plan/createplan.c
  */
 extern Plan *create_plan(PlannerInfo *root, Path *best_path);
-extern Plan *create_plan_recurse(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);
+				 Index scanrelid, List *fdw_exprs, List *fdw_private,
+				 List *fdw_scan_tlist);
 extern Append *make_append(List *appendplans, List *tlist);
 extern RecursiveUnion *make_recursive_union(List *tlist,
 					 Plan *lefttree, Plan *righttree, int wtParam,