diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index a75462b..f6d6936 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -86,7 +86,7 @@ typedef struct foreign_loc_cxt
 typedef struct deparse_expr_cxt
 {
 	PlannerInfo *root;			/* global planner state */
-	RelOptInfo *foreignrel;		/* the foreign relation we are planning for */
+	Relids		rels;			/* list of foreign tables to be deparsed */
 	StringInfo	buf;			/* output buffer to append to */
 	List	  **params_list;	/* exprs that will become remote Params */
 } deparse_expr_cxt;
@@ -108,6 +108,7 @@ static void deparseTargetList(StringInfo buf,
 				  Index rtindex,
 				  Relation rel,
 				  Bitmapset *attrs_used,
+				  const char *alias,
 				  List **retrieved_attrs);
 static void deparseReturningList(StringInfo buf, PlannerInfo *root,
 					 Index rtindex, Relation rel,
@@ -115,7 +116,7 @@ static void deparseReturningList(StringInfo buf, PlannerInfo *root,
 					 List *returningList,
 					 List **retrieved_attrs);
 static void deparseColumnRef(StringInfo buf, int varno, int varattno,
-				 PlannerInfo *root);
+				 PlannerInfo *root, const char *alias);
 static void deparseRelation(StringInfo buf, Relation rel);
 static void deparseExpr(Expr *expr, deparse_expr_cxt *context);
 static void deparseVar(Var *node, deparse_expr_cxt *context);
@@ -679,33 +680,119 @@ is_builtin(Oid oid)
 void
 deparseSelectSql(StringInfo buf,
 				 PlannerInfo *root,
-				 RelOptInfo *baserel,
-				 Bitmapset *attrs_used,
-				 List **retrieved_attrs)
+				 List *rels)
 {
-	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
-	Relation	rel;
+	StringInfoData frombuf;
+	ListCell   *lc;
+	bool		first_rel = true;
+	Relids		relids = NULL;
 
-	/*
-	 * Core code already has some lock on each rel being planned, so we can
-	 * use NoLock here.
-	 */
-	rel = heap_open(rte->relid, NoLock);
+	initStringInfo(&frombuf);
 
-	/*
-	 * Construct SELECT list
-	 */
-	appendStringInfoString(buf, "SELECT ");
-	deparseTargetList(buf, root, baserel->relid, rel, attrs_used,
-					  retrieved_attrs);
+	/* Construct list of relid for deparsing query contains multiple tables. */
+	foreach(lc, rels)
+	{
+		PgFdwDeparseRel *dr = (PgFdwDeparseRel *) lfirst(lc);
+		relids = bms_add_member(relids, dr->baserel->relid);
+	}
 
-	/*
-	 * Construct FROM clause
-	 */
-	appendStringInfoString(buf, " FROM ");
-	deparseRelation(buf, rel);
+	/* Loop through relation list and deparse SELECT query. */
+	foreach(lc, rels)
+	{
+		PgFdwDeparseRel *dr = (PgFdwDeparseRel *) lfirst(lc);
+		RangeTblEntry *rte = planner_rt_fetch(dr->baserel->relid, root);
+		Relation	rel;
+		const char *alias;
 
-	heap_close(rel, NoLock);
+		/*
+		 * Core code already has some lock on each rel being planned, so we can
+		 * use NoLock here.
+		 */
+		rel = heap_open(rte->relid, NoLock);
+
+		/*
+		 * Add alias only when we have multiple relations.
+		 */
+		if (list_length(rels) > 1 && rte->alias)
+			alias = rte->alias->aliasname;
+		else
+			alias = NULL;
+
+		/*
+		 * Construct SELECT list
+		 */
+		if (first_rel)
+			appendStringInfoString(buf, "SELECT ");
+		else
+			appendStringInfoString(buf, ", ");
+		deparseTargetList(buf, root, dr->baserel->relid, rel, dr->attrs_used,
+						  alias, dr->retrieved_attrs);
+
+		/*
+		 * Construct FROM clause
+		 */
+		if (first_rel)
+			appendStringInfoString(&frombuf, " FROM ");
+		else
+		{
+			switch (dr->jointype)
+			{
+				case JOIN_INNER:
+					if (dr->joinclauses)
+						appendStringInfoString(&frombuf, " INNER JOIN ");
+					else
+						/* Currently cross join is not pushed down, though. */
+						appendStringInfoString(&frombuf, " CROSS JOIN ");
+					break;
+				case JOIN_LEFT:
+					appendStringInfoString(&frombuf, " LEFT JOIN ");
+					break;
+				case JOIN_FULL:
+					appendStringInfoString(&frombuf, " FULL JOIN ");
+					break;
+				case JOIN_RIGHT:
+					appendStringInfoString(&frombuf, " RIGHT JOIN ");
+					break;
+				default:
+					elog(ERROR, "unsupported join type for deparse: %d",
+						 dr->jointype);
+					break;
+			}
+		}
+		deparseRelation(&frombuf, rel);
+		if (alias)
+			appendStringInfo(&frombuf, " %s", alias);
+
+		if (!first_rel && dr->joinclauses)
+		{
+			ListCell   *lc;
+			bool		first = true;
+
+			appendStringInfoString(&frombuf, " ON ");
+
+			foreach(lc, dr->joinclauses)
+			{
+				deparse_expr_cxt context;
+				Expr *expr = (Expr *) lfirst(lc);
+
+				context.root = root;
+				context.rels = relids;
+				context.buf = &frombuf;
+				context.params_list = NULL;
+
+				if (!first)
+					appendStringInfoString(&frombuf, " AND ");
+				deparseExpr(expr, &context);
+				first = false;
+			}
+		}
+
+		heap_close(rel, NoLock);
+		first_rel = false;
+	}
+
+	appendStringInfoString(buf, frombuf.data);
+	pfree(frombuf.data);
 }
 
 /*
@@ -721,6 +808,7 @@ deparseTargetList(StringInfo buf,
 				  Index rtindex,
 				  Relation rel,
 				  Bitmapset *attrs_used,
+				  const char *alias,
 				  List **retrieved_attrs)
 {
 	TupleDesc	tupdesc = RelationGetDescr(rel);
@@ -751,7 +839,7 @@ deparseTargetList(StringInfo buf,
 				appendStringInfoString(buf, ", ");
 			first = false;
 
-			deparseColumnRef(buf, rtindex, i, root);
+			deparseColumnRef(buf, rtindex, i, root, alias);
 
 			*retrieved_attrs = lappend_int(*retrieved_attrs, i);
 		}
@@ -768,6 +856,8 @@ deparseTargetList(StringInfo buf,
 			appendStringInfoString(buf, ", ");
 		first = false;
 
+		if (alias)
+			appendStringInfo(buf, "%s.", alias);
 		appendStringInfoString(buf, "ctid");
 
 		*retrieved_attrs = lappend_int(*retrieved_attrs,
@@ -796,7 +886,7 @@ deparseTargetList(StringInfo buf,
 void
 appendWhereClause(StringInfo buf,
 				  PlannerInfo *root,
-				  RelOptInfo *baserel,
+				  Relids relids,
 				  List *exprs,
 				  bool is_first,
 				  List **params)
@@ -810,7 +900,7 @@ appendWhereClause(StringInfo buf,
 
 	/* Set up context struct for recursion */
 	context.root = root;
-	context.foreignrel = baserel;
+	context.rels = relids;
 	context.buf = buf;
 	context.params_list = params;
 
@@ -870,7 +960,7 @@ deparseInsertSql(StringInfo buf, PlannerInfo *root,
 				appendStringInfoString(buf, ", ");
 			first = false;
 
-			deparseColumnRef(buf, rtindex, attnum, root);
+			deparseColumnRef(buf, rtindex, attnum, root, NULL);
 		}
 
 		appendStringInfoString(buf, ") VALUES (");
@@ -928,7 +1018,7 @@ deparseUpdateSql(StringInfo buf, PlannerInfo *root,
 			appendStringInfoString(buf, ", ");
 		first = false;
 
-		deparseColumnRef(buf, rtindex, attnum, root);
+		deparseColumnRef(buf, rtindex, attnum, root, NULL);
 		appendStringInfo(buf, " = $%d", pindex);
 		pindex++;
 	}
@@ -993,7 +1083,7 @@ deparseReturningList(StringInfo buf, PlannerInfo *root,
 	if (attrs_used != NULL)
 	{
 		appendStringInfoString(buf, " RETURNING ");
-		deparseTargetList(buf, root, rtindex, rel, attrs_used,
+		deparseTargetList(buf, root, rtindex, rel, attrs_used, NULL,
 						  retrieved_attrs);
 	}
 	else
@@ -1088,7 +1178,8 @@ deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs)
  * If it has a column_name FDW option, use that instead of attribute name.
  */
 static void
-deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root)
+deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root,
+				 const char *alias)
 {
 	RangeTblEntry *rte;
 	char	   *colname = NULL;
@@ -1124,6 +1215,8 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root)
 	if (colname == NULL)
 		colname = get_relid_attribute_name(rte->relid, varattno);
 
+	if (alias)
+		appendStringInfo(buf, "%s.", alias);
 	appendStringInfoString(buf, quote_identifier(colname));
 }
 
@@ -1270,12 +1363,46 @@ static void
 deparseVar(Var *node, deparse_expr_cxt *context)
 {
 	StringInfo	buf = context->buf;
+	int			i;
+	RelOptInfo *rel = NULL;
+	RangeTblEntry *rte = NULL;
 
-	if (node->varno == context->foreignrel->relid &&
-		node->varlevelsup == 0)
+	/* Find RangeTblEntry contains given Var to determine alias name. */
+	if (bms_is_member(node->varno, context->rels) && node->varlevelsup == 0)
 	{
-		/* Var belongs to foreign table */
-		deparseColumnRef(buf, node->varno, node->varattno, context->root);
+		for (i = 1; i < context->root->simple_rel_array_size; i++)
+		{
+			/* Skip empty slot */
+			if (context->root->simple_rel_array[i] == NULL)
+				continue;
+
+			if (context->root->simple_rel_array[i]->relid == node->varno)
+			{
+				rel = context->root->simple_rel_array[i];
+				rte = context->root->simple_rte_array[i];
+				break;
+			}
+		}
+	}
+
+	/*
+	 * If the Var is in current level (not in outer subquery), simply deparse
+	 * it.
+	 */
+	if (rel)
+	{
+		const char *alias;
+
+		/*
+		 * Deparse Var belongs to foreign tables in context->rels, with alias
+		 * name if we are deparsing multiple foreign tables.
+		 */
+		if (bms_num_members(context->rels) > 1 && rte->alias)
+			alias = rte->alias->aliasname;
+		else
+			alias = NULL;
+		deparseColumnRef(buf, node->varno, node->varattno, context->root,
+						 alias);
 	}
 	else
 	{
@@ -1849,3 +1976,4 @@ printRemotePlaceholder(Oid paramtype, int32 paramtypmod,
 
 	appendStringInfo(buf, "((SELECT null::%s)::%s)", ptypename, ptypename);
 }
+
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index c3039a6..aaae2bb 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -288,6 +288,16 @@ static bool postgresAnalyzeForeignTable(Relation relation,
 							BlockNumber *totalpages);
 static List *postgresImportForeignSchema(ImportForeignSchemaStmt *stmt,
 							Oid serverOid);
+static void postgresGetForeignJoinPath(PlannerInfo *root,
+						   RelOptInfo *joinrel,
+						   RelOptInfo *outerrel,
+						   RelOptInfo *innerrel,
+						   List *restrictlisti,
+						   JoinType jointype,
+						   SpecialJoinInfo *sjinfo,
+						   SemiAntiJoinFactors *semifactors,
+						   Relids param_source_rels,
+						   Relids extra_lateral_rels);
 
 /*
  * Helper functions
@@ -329,6 +339,17 @@ static HeapTuple make_tuple_from_result_row(PGresult *res,
 						   MemoryContext temp_context);
 static void conversion_error_callback(void *arg);
 
+extern void _PG_init(void);
+
+static set_join_pathlist_hook_type prev_set_join_pathlist_hook;
+
+void
+_PG_init(void)
+{
+	prev_set_join_pathlist_hook = set_join_pathlist_hook;
+	set_join_pathlist_hook = postgresGetForeignJoinPath;
+}
+
 
 /*
  * Foreign-data wrapper handler function: return a struct with pointers
@@ -752,6 +773,7 @@ postgresGetForeignPlan(PlannerInfo *root,
 	List	   *retrieved_attrs;
 	StringInfoData sql;
 	ListCell   *lc;
+	PgFdwDeparseRel dr;
 
 	/*
 	 * Separate the scan_clauses into those that can be executed remotely and
@@ -797,11 +819,15 @@ postgresGetForeignPlan(PlannerInfo *root,
 	 * expressions to be sent as parameters.
 	 */
 	initStringInfo(&sql);
-	deparseSelectSql(&sql, root, baserel, fpinfo->attrs_used,
-					 &retrieved_attrs);
+	dr.baserel = baserel;
+	dr.jointype = JOIN_INNER;
+	dr.joinclauses = NIL;
+	dr.attrs_used = fpinfo->attrs_used;
+	dr.retrieved_attrs = &retrieved_attrs;
+	deparseSelectSql(&sql, root, list_make1(&dr));
 	if (remote_conds)
-		appendWhereClause(&sql, root, baserel, remote_conds,
-						  true, &params_list);
+		appendWhereClause(&sql, root, bms_add_member(NULL, baserel->relid),
+						  remote_conds, true, &params_list);
 
 	/*
 	 * Add FOR UPDATE/SHARE if appropriate.  We apply locking during the
@@ -1725,10 +1751,12 @@ estimate_path_cost_size(PlannerInfo *root,
 		List	   *remote_join_conds;
 		List	   *local_join_conds;
 		StringInfoData sql;
+		Relids		relids;
 		List	   *retrieved_attrs;
 		PGconn	   *conn;
 		Selectivity local_sel;
 		QualCost	local_cost;
+		PgFdwDeparseRel dr;
 
 		/*
 		 * join_conds might contain both clauses that are safe to send across,
@@ -1743,14 +1771,19 @@ estimate_path_cost_size(PlannerInfo *root,
 		 * dummy values.
 		 */
 		initStringInfo(&sql);
+		dr.baserel = baserel;
+		dr.jointype = JOIN_INNER;
+		dr.joinclauses = NIL;
+		dr.attrs_used = fpinfo->attrs_used;
+		dr.retrieved_attrs = &retrieved_attrs;
 		appendStringInfoString(&sql, "EXPLAIN ");
-		deparseSelectSql(&sql, root, baserel, fpinfo->attrs_used,
-						 &retrieved_attrs);
+		deparseSelectSql(&sql, root, list_make1(&dr));
+		relids = bms_add_member(NULL, baserel->relid);
 		if (fpinfo->remote_conds)
-			appendWhereClause(&sql, root, baserel, fpinfo->remote_conds,
+			appendWhereClause(&sql, root, relids, fpinfo->remote_conds,
 							  true, NULL);
 		if (remote_join_conds)
-			appendWhereClause(&sql, root, baserel, remote_join_conds,
+			appendWhereClause(&sql, root, relids, remote_join_conds,
 							  (fpinfo->remote_conds == NIL), NULL);
 
 		/* Get the remote estimate */
@@ -2835,6 +2868,118 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
 }
 
 /*
+ * postgresGetForeignJoinPath
+ *		Add possible ForeignJoinPath to joinrel.
+ *
+ */
+static void
+postgresGetForeignJoinPath(PlannerInfo *root,
+						   RelOptInfo *joinrel,
+						   RelOptInfo *outerrel,
+						   RelOptInfo *innerrel,
+						   List *restrictlist,
+						   JoinType jointype,
+						   SpecialJoinInfo *sjinfo,
+						   SemiAntiJoinFactors *semifactors,
+						   Relids param_source_rels,
+						   Relids extra_lateral_rels)
+{
+	int				i;
+	bool			rte_found;
+	Oid				serverid = InvalidOid;
+	Oid				checkAsUser = InvalidOid;
+	Path		   *outerpath = outerrel->cheapest_total_path;
+	Path		   *innerpath = innerrel->cheapest_total_path;
+	ForeignJoinPath *joinpath;
+	Relids			required_outer;
+	List		   *fdw_private = NIL;
+
+	/* Skip considering reversed join combination */
+	elog(DEBUG1, "%s() outer: %d, inner: %d",
+		 __func__, outerrel->relid, innerrel->relid);
+	if (outerrel->relid < innerrel->relid)
+		return;
+
+	/* At the moment we support only joins between foreign tables. */
+	if (outerrel->reloptkind != RELOPT_BASEREL ||
+		innerrel->reloptkind != RELOPT_BASEREL)
+		return;
+
+	/*
+	 * We support all outer joins in addition to inner join.
+	 */
+	if (jointype != JOIN_INNER && jointype != JOIN_LEFT &&
+		jointype != JOIN_RIGHT && jointype != JOIN_FULL)
+		return;
+
+	/*
+	 * Note that CROSS JOIN (cartesian product) is transformed to JOIN_INNER
+	 * with empty restrictlist. Pushing down CROSS JOIN produces more result
+	 * than retrieving each tables separately, so we don't push down such joins.
+	 */
+	if (jointype == JOIN_INNER && !restrictlist)
+		return;
+
+	/*
+	 * All relations in the join must belong to same server, and have same
+	 * checkAsUser to use one connection to execute SQL for the join.
+	 */
+	rte_found = false;
+	for (i = 1; i < root->simple_rel_array_size; i++)
+	{
+		RangeTblEntry *rte;
+		ForeignTable *ft;
+
+		if (!bms_is_member(i, joinrel->relids))
+			continue;
+
+		rte = root->simple_rte_array[i];
+		if (rte == NULL)
+			continue;
+
+		ft = GetForeignTable(rte->relid);
+		if (rte_found)
+		{
+			if (serverid != ft->serverid)
+				return;
+			if (checkAsUser != rte->checkAsUser)
+				return;
+		}
+		else
+		{
+			checkAsUser = rte->checkAsUser;
+			serverid = ft->serverid;
+			rte_found = true;
+		}
+	}
+	fdw_private = lappend_oid(fdw_private, checkAsUser);
+
+	/*
+	 * Create a new join path and add it to the joinrel which represents a join
+	 * between foreign tables.
+	 */
+	required_outer = calc_non_nestloop_required_outer(outerpath, innerpath);
+	joinpath = create_foreignjoin_path(root,
+									   joinrel,
+									   jointype,
+									   sjinfo,
+									   semifactors,
+									   outerpath,
+									   innerpath,
+									   restrictlist,
+									   NIL,
+									   required_outer);
+
+	/* TODO determine cost and rows of the join. */
+
+	/* Add generated path into joinrel by add_path(). */
+	add_path(joinrel, (Path *) joinpath);
+
+	/* TODO consider parameterized paths */
+}
+
+
+/*
  * Create a tuple from the specified row of the PGresult.
  *
  * rel is the local representation of the foreign table, attinmeta is
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 0382c55..dcfcb77 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -39,6 +39,13 @@ extern int ExtractConnectionOptions(List *defelems,
 						 const char **values);
 
 /* in deparse.c */
+typedef struct PgFdwDeparseRel {
+	RelOptInfo	   *baserel;
+	JoinType		jointype;
+	List		   *joinclauses;
+	Bitmapset	   *attrs_used;
+	List		  **retrieved_attrs;
+} PgFdwDeparseRel;
 extern void classifyConditions(PlannerInfo *root,
 				   RelOptInfo *baserel,
 				   List *input_conds,
@@ -49,12 +56,10 @@ extern bool is_foreign_expr(PlannerInfo *root,
 				Expr *expr);
 extern void deparseSelectSql(StringInfo buf,
 				 PlannerInfo *root,
-				 RelOptInfo *baserel,
-				 Bitmapset *attrs_used,
-				 List **retrieved_attrs);
+				 List *rels);
 extern void appendWhereClause(StringInfo buf,
 				  PlannerInfo *root,
-				  RelOptInfo *baserel,
+				  Relids relids,
 				  List *exprs,
 				  bool is_first,
 				  List **params);
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 457cbab..7023a40 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -250,6 +250,29 @@ GetForeignTable(Oid relid)
 
 
 /*
+ * GetForeignTableServerOid - Get OID of the server related to the given
+ * foreign table.
+ */
+Oid
+GetForeignTableServerOid(Oid relid)
+{
+	Form_pg_foreign_table tableform;
+	HeapTuple	tp;
+	Oid			serverid;
+
+	tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for foreign table %u", relid);
+	tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
+	serverid = tableform->ftserver;
+
+	ReleaseSysCache(tp);
+
+	return serverid;
+}
+
+
+/*
  * GetForeignColumnOptions - Get attfdwoptions of given relation/attnum
  * as list of DefElem.
  */
@@ -309,11 +332,15 @@ GetFdwRoutine(Oid fdwhandler)
 FdwRoutine *
 GetFdwRoutineByServer(Oid serverid)
 {
+	return GetFdwRoutineByServerId(serverid);
+}
+
+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));
@@ -323,6 +350,16 @@ GetFdwRoutineByServer(Oid serverid)
 	fdwid = serverform->srvfdw;
 	ReleaseSysCache(tp);
 
+	return GetFdwRoutineByFdwId(fdwid);
+}
+
+FdwRoutine *
+GetFdwRoutineByFdwId(Oid fdwid)
+{
+	HeapTuple	tp;
+	Form_pg_foreign_data_wrapper fdwform;
+	Oid			fdwhandler;
+
 	/* Get handler function OID for the FDW. */
 	tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
 	if (!HeapTupleIsValid(tp))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d799eb8..b9ef533 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1702,6 +1702,16 @@ _outHashPath(StringInfo str, const HashPath *node)
 }
 
 static void
+_outForeignJoinPath(StringInfo str, const ForeignJoinPath *node)
+{
+	WRITE_NODE_TYPE("FOREIGNJOINPATH");
+
+	_outJoinPathInfo(str, (const JoinPath *) node);
+
+	WRITE_NODE_FIELD(fdw_private);
+}
+
+static void
 _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 {
 	WRITE_NODE_TYPE("PLANNERGLOBAL");
@@ -1800,6 +1810,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node)
 	WRITE_NODE_FIELD(subplan);
 	WRITE_NODE_FIELD(subroot);
 	WRITE_NODE_FIELD(subplan_params);
+	WRITE_OID_FIELD(fdwid);
 	/* we don't try to print fdwroutine or fdw_private */
 	WRITE_NODE_FIELD(baserestrictinfo);
 	WRITE_NODE_FIELD(joininfo);
@@ -3124,6 +3135,9 @@ _outNode(StringInfo str, const void *obj)
 			case T_HashPath:
 				_outHashPath(str, obj);
 				break;
+			case T_ForeignJoinPath:
+				_outForeignJoinPath(str, obj);
+				break;
 			case T_PlannerGlobal:
 				_outPlannerGlobal(str, obj);
 				break;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 659daa2..637e2d8 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -1782,8 +1782,8 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path,
 					SpecialJoinInfo *sjinfo,
 					SemiAntiJoinFactors *semifactors)
 {
-	Path	   *outer_path = path->outerjoinpath;
-	Path	   *inner_path = path->innerjoinpath;
+	Path	   *outer_path = path->jpath.outerjoinpath;
+	Path	   *inner_path = path->jpath.innerjoinpath;
 	double		outer_path_rows = outer_path->rows;
 	double		inner_path_rows = inner_path->rows;
 	Cost		startup_cost = workspace->startup_cost;
@@ -1794,10 +1794,10 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path,
 	double		ntuples;
 
 	/* Mark the path with the correct row estimate */
-	if (path->path.param_info)
-		path->path.rows = path->path.param_info->ppi_rows;
+	if (path->jpath.path.param_info)
+		path->jpath.path.rows = path->jpath.path.param_info->ppi_rows;
 	else
-		path->path.rows = path->path.parent->rows;
+		path->jpath.path.rows = path->jpath.path.parent->rows;
 
 	/*
 	 * We could include disable_cost in the preliminary estimate, but that
@@ -1809,7 +1809,7 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path,
 
 	/* cost of source data */
 
-	if (path->jointype == JOIN_SEMI || path->jointype == JOIN_ANTI)
+	if (path->jpath.jointype == JOIN_SEMI || path->jpath.jointype == JOIN_ANTI)
 	{
 		double		outer_matched_rows = workspace->outer_matched_rows;
 		Selectivity inner_scan_frac = workspace->inner_scan_frac;
@@ -1856,13 +1856,13 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path,
 	}
 
 	/* CPU costs */
-	cost_qual_eval(&restrict_qual_cost, path->joinrestrictinfo, root);
+	cost_qual_eval(&restrict_qual_cost, path->jpath.joinrestrictinfo, root);
 	startup_cost += restrict_qual_cost.startup;
 	cpu_per_tuple = cpu_tuple_cost + restrict_qual_cost.per_tuple;
 	run_cost += cpu_per_tuple * ntuples;
 
-	path->path.startup_cost = startup_cost;
-	path->path.total_cost = startup_cost + run_cost;
+	path->jpath.path.startup_cost = startup_cost;
+	path->jpath.path.total_cost = startup_cost + run_cost;
 }
 
 /*
@@ -3306,14 +3306,14 @@ compute_semi_anti_join_factors(PlannerInfo *root,
 static bool
 has_indexed_join_quals(NestPath *joinpath)
 {
-	Relids		joinrelids = joinpath->path.parent->relids;
-	Path	   *innerpath = joinpath->innerjoinpath;
+	Relids		joinrelids = joinpath->jpath.path.parent->relids;
+	Path	   *innerpath = joinpath->jpath.innerjoinpath;
 	List	   *indexclauses;
 	bool		found_one;
 	ListCell   *lc;
 
 	/* If join still has quals to evaluate, it's not fast */
-	if (joinpath->joinrestrictinfo != NIL)
+	if (joinpath->jpath.joinrestrictinfo != NIL)
 		return false;
 	/* Nor if the inner path isn't parameterized at all */
 	if (innerpath->param_info == NULL)
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 030158d..29ee414 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -17,6 +17,7 @@
 #include <math.h>
 
 #include "executor/executor.h"
+#include "foreign/fdwapi.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -52,7 +53,6 @@ static List *select_mergejoin_clauses(PlannerInfo *root,
 						 JoinType jointype,
 						 bool *mergejoin_allowed);
 
-
 /*
  * add_paths_to_joinrel
  *	  Given a join relation and two component rels from which it can be made,
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 9683560..fc3ef81 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -83,11 +83,11 @@ static CustomScan *create_customscan_plan(PlannerInfo *root,
 					   CustomPath *best_path,
 					   List *tlist, List *scan_clauses);
 static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path,
-					 Plan *outer_plan, Plan *inner_plan);
+					 List *tlist, Plan *outer_plan, Plan *inner_plan);
 static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
-					  Plan *outer_plan, Plan *inner_plan);
+					  List *tlist, Plan *outer_plan, Plan *inner_plan);
 static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path,
-					 Plan *outer_plan, Plan *inner_plan);
+					 List *tlist, Plan *outer_plan, Plan *inner_plan);
 static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
 static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
 static void process_subquery_nestloop_params(PlannerInfo *root,
@@ -238,6 +238,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 		case T_CteScan:
 		case T_WorkTableScan:
 		case T_ForeignScan:
+		case T_ForeignJoinPath:
 		case T_CustomScan:
 			plan = create_scan_plan(root, best_path);
 			break;
@@ -409,6 +410,7 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
 			break;
 
 		case T_ForeignScan:
+		case T_ForeignJoinPath:
 			plan = (Plan *) create_foreignscan_plan(root,
 													(ForeignPath *) best_path,
 													tlist,
@@ -611,6 +613,7 @@ create_gating_plan(PlannerInfo *root, Plan *plan, List *quals)
 static Plan *
 create_join_plan(PlannerInfo *root, JoinPath *best_path)
 {
+	List	   *tlist;
 	Plan	   *outer_plan;
 	Plan	   *inner_plan;
 	Plan	   *plan;
@@ -625,27 +628,34 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path)
 
 	inner_plan = create_plan_recurse(root, best_path->innerjoinpath);
 
+	if (best_path->path.pathtype == T_NestLoop)
+	{
+		/* Restore curOuterRels */
+		bms_free(root->curOuterRels);
+		root->curOuterRels = saveOuterRels;
+	}
+   	tlist = build_path_tlist(root, &best_path->path);
+
 	switch (best_path->path.pathtype)
 	{
 		case T_MergeJoin:
 			plan = (Plan *) create_mergejoin_plan(root,
 												  (MergePath *) best_path,
+												  tlist,
 												  outer_plan,
 												  inner_plan);
 			break;
 		case T_HashJoin:
 			plan = (Plan *) create_hashjoin_plan(root,
 												 (HashPath *) best_path,
+												 tlist,
 												 outer_plan,
 												 inner_plan);
 			break;
 		case T_NestLoop:
-			/* Restore curOuterRels */
-			bms_free(root->curOuterRels);
-			root->curOuterRels = saveOuterRels;
-
 			plan = (Plan *) create_nestloop_plan(root,
 												 (NestPath *) best_path,
+												 tlist,
 												 outer_plan,
 												 inner_plan);
 			break;
@@ -2115,12 +2125,12 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
 static NestLoop *
 create_nestloop_plan(PlannerInfo *root,
 					 NestPath *best_path,
+					 List *tlist,
 					 Plan *outer_plan,
 					 Plan *inner_plan)
 {
 	NestLoop   *join_plan;
-	List	   *tlist = build_path_tlist(root, &best_path->path);
-	List	   *joinrestrictclauses = best_path->joinrestrictinfo;
+	List	   *joinrestrictclauses = best_path->jpath.joinrestrictinfo;
 	List	   *joinclauses;
 	List	   *otherclauses;
 	Relids		outerrelids;
@@ -2134,7 +2144,7 @@ create_nestloop_plan(PlannerInfo *root,
 
 	/* Get the join qual clauses (in plain expression form) */
 	/* Any pseudoconstant clauses are ignored here */
-	if (IS_OUTER_JOIN(best_path->jointype))
+	if (IS_OUTER_JOIN(best_path->jpath.jointype))
 	{
 		extract_actual_join_clauses(joinrestrictclauses,
 									&joinclauses, &otherclauses);
@@ -2147,7 +2157,7 @@ create_nestloop_plan(PlannerInfo *root,
 	}
 
 	/* Replace any outer-relation variables with nestloop params */
-	if (best_path->path.param_info)
+	if (best_path->jpath.path.param_info)
 	{
 		joinclauses = (List *)
 			replace_nestloop_params(root, (Node *) joinclauses);
@@ -2159,7 +2169,7 @@ create_nestloop_plan(PlannerInfo *root,
 	 * Identify any nestloop parameters that should be supplied by this join
 	 * node, and move them from root->curOuterParams to the nestParams list.
 	 */
-	outerrelids = best_path->outerjoinpath->parent->relids;
+	outerrelids = best_path->jpath.outerjoinpath->parent->relids;
 	nestParams = NIL;
 	prev = NULL;
 	for (cell = list_head(root->curOuterParams); cell; cell = next)
@@ -2196,9 +2206,9 @@ create_nestloop_plan(PlannerInfo *root,
 							  nestParams,
 							  outer_plan,
 							  inner_plan,
-							  best_path->jointype);
+							  best_path->jpath.jointype);
 
-	copy_path_costsize(&join_plan->join.plan, &best_path->path);
+	copy_path_costsize(&join_plan->join.plan, &best_path->jpath.path);
 
 	return join_plan;
 }
@@ -2206,10 +2216,10 @@ create_nestloop_plan(PlannerInfo *root,
 static MergeJoin *
 create_mergejoin_plan(PlannerInfo *root,
 					  MergePath *best_path,
+					  List *tlist,
 					  Plan *outer_plan,
 					  Plan *inner_plan)
 {
-	List	   *tlist = build_path_tlist(root, &best_path->jpath.path);
 	List	   *joinclauses;
 	List	   *otherclauses;
 	List	   *mergeclauses;
@@ -2501,10 +2511,10 @@ create_mergejoin_plan(PlannerInfo *root,
 static HashJoin *
 create_hashjoin_plan(PlannerInfo *root,
 					 HashPath *best_path,
+					 List *tlist,
 					 Plan *outer_plan,
 					 Plan *inner_plan)
 {
-	List	   *tlist = build_path_tlist(root, &best_path->jpath.path);
 	List	   *joinclauses;
 	List	   *otherclauses;
 	List	   *hashclauses;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 319e8b2..dd81764 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1710,9 +1710,9 @@ create_nestloop_path(PlannerInfo *root,
 		restrict_clauses = jclauses;
 	}
 
-	pathnode->path.pathtype = T_NestLoop;
-	pathnode->path.parent = joinrel;
-	pathnode->path.param_info =
+	pathnode->jpath.path.pathtype = T_NestLoop;
+	pathnode->jpath.path.parent = joinrel;
+	pathnode->jpath.path.param_info =
 		get_joinrel_parampathinfo(root,
 								  joinrel,
 								  outer_path,
@@ -1720,11 +1720,11 @@ create_nestloop_path(PlannerInfo *root,
 								  sjinfo,
 								  required_outer,
 								  &restrict_clauses);
-	pathnode->path.pathkeys = pathkeys;
-	pathnode->jointype = jointype;
-	pathnode->outerjoinpath = outer_path;
-	pathnode->innerjoinpath = inner_path;
-	pathnode->joinrestrictinfo = restrict_clauses;
+	pathnode->jpath.path.pathkeys = pathkeys;
+	pathnode->jpath.jointype = jointype;
+	pathnode->jpath.outerjoinpath = outer_path;
+	pathnode->jpath.innerjoinpath = inner_path;
+	pathnode->jpath.joinrestrictinfo = restrict_clauses;
 
 	final_cost_nestloop(root, pathnode, workspace, sjinfo, semifactors);
 
@@ -1859,6 +1859,58 @@ create_hashjoin_path(PlannerInfo *root,
 }
 
 /*
+ * create_foreignjoin_path
+ *	  Creates a pathnode corresponding to a foreign join between two relations.
+ *	  Unlike similar funcitons for other join types, final_cost_foreignjoin is
+ *	  not called, so FDW have to take care of cost information.
+ *
+ * 'joinrel' is the join relation
+ * 'jointype' is the type of join required
+ * 'sjinfo' is extra info about the join for selectivity estimation
+ * 'semifactors' contains valid data if jointype is SEMI or ANTI
+ * 'outer_path' is the cheapest outer path
+ * 'inner_path' is the cheapest inner path
+ * 'restrict_clauses' are the RestrictInfo nodes to apply at the join
+ * 'required_outer' is the set of required outer rels
+ * 'foreignclauses' are the RestrictInfo nodes to use as foreign clauses
+ *		(this should be a subset of the restrict_clauses list)
+ */
+ForeignJoinPath *
+create_foreignjoin_path(PlannerInfo *root,
+						RelOptInfo *joinrel,
+						JoinType jointype,
+						SpecialJoinInfo *sjinfo,
+						SemiAntiJoinFactors *semifactors,
+						Path *outer_path,
+						Path *inner_path,
+						List *restrict_clauses,
+						List *pathkeys,
+						Relids required_outer)
+{
+	ForeignJoinPath *pathnode = makeNode(ForeignJoinPath);
+
+	pathnode->jpath.path.pathtype = T_ForeignJoinPath;
+	pathnode->jpath.path.parent = joinrel;
+	pathnode->jpath.path.param_info =
+		get_joinrel_parampathinfo(root,
+								  joinrel,
+								  outer_path,
+								  inner_path,
+								  sjinfo,
+								  required_outer,
+								  &restrict_clauses);
+	pathnode->jpath.path.pathkeys = pathkeys;
+	pathnode->jpath.jointype = jointype;
+	pathnode->jpath.outerjoinpath = outer_path;
+	pathnode->jpath.innerjoinpath = inner_path;
+	pathnode->jpath.joinrestrictinfo = restrict_clauses;
+
+	pathnode->fdw_private = NIL;
+
+	return pathnode;
+}
+
+/*
  * reparameterize_path
  *		Attempt to modify a Path to have greater parameterization
  *
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 627bc53..2e5d91d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
 #include "foreign/fdwapi.h"
+#include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 0429c76..93d7e79 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -121,6 +121,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	rel->subplan = NULL;
 	rel->subroot = NULL;
 	rel->subplan_params = NIL;
+	rel->fdwid = InvalidOid;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
 	rel->baserestrictinfo = NIL;
@@ -383,7 +384,17 @@ build_join_rel(PlannerInfo *root,
 	joinrel->subplan = NULL;
 	joinrel->subroot = NULL;
 	joinrel->subplan_params = NIL;
-	joinrel->fdwroutine = NULL;
+	/* propagate common server information up to join relation */
+	if (inner_rel->fdwid == outer_rel->fdwid)
+	{
+		joinrel->fdwroutine = inner_rel->fdwroutine;
+		joinrel->fdwid = inner_rel->fdwid;
+	}
+	else
+	{
+		joinrel->fdwid = InvalidOid;
+		joinrel->fdwroutine = NULL;
+	}
 	joinrel->fdw_private = NULL;
 	joinrel->baserestrictinfo = NIL;
 	joinrel->baserestrictcost.startup = 0;
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 0faad55..0f713fc 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -82,6 +82,24 @@ typedef void (*EndForeignModify_function) (EState *estate,
 
 typedef int (*IsForeignRelUpdatable_function) (Relation rel);
 
+typedef void (*GetForeignJoinPath_function ) (PlannerInfo *root,
+											  RelOptInfo *joinrel,
+											  RelOptInfo *outerrel,
+											  RelOptInfo *innerrel,
+											  JoinType jointype,
+											  SpecialJoinInfo *sjinfo,
+											  SemiAntiJoinFactors *semifactors,
+											  List *restrictlist,
+											  Relids extra_lateral_rels);
+
+typedef ForeignScan *(*GetForeignJoinPlan_function) (PlannerInfo *root,
+													 ForeignJoinPath *best_path,
+													 List *tlist,
+													 List *joinclauses,
+													 List *otherclauses,
+													 Plan *outer_plan,
+													 Plan *inner_plan);
+
 typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
 													struct ExplainState *es);
 
@@ -157,6 +175,8 @@ typedef struct FdwRoutine
 extern FdwRoutine *GetFdwRoutine(Oid fdwhandler);
 extern FdwRoutine *GetFdwRoutineByServer(Oid server_id);
 extern FdwRoutine *GetFdwRoutineByRelId(Oid relid);
+extern FdwRoutine *GetFdwRoutineByServerId(Oid serverid);
+extern FdwRoutine *GetFdwRoutineByFdwId(Oid fdwid);
 extern FdwRoutine *GetFdwRoutineForRelation(Relation relation, bool makecopy);
 extern Oid	GetForeignServerForRelation(Relation relation);
 extern bool IsImportableForeignTable(const char *tablename,
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index ac080d7..b9e120a 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -75,6 +75,7 @@ extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
 extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
 							bool missing_ok);
 extern ForeignTable *GetForeignTable(Oid relid);
+extern Oid GetForeignTableServerOid(Oid relid);
 
 extern List *GetForeignColumnOptions(Oid relid, AttrNumber attnum);
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index bc71fea..160c0f6 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -224,6 +224,7 @@ typedef enum NodeTag
 	T_NestPath,
 	T_MergePath,
 	T_HashPath,
+	T_ForeignJoinPath,
 	T_TidPath,
 	T_ForeignPath,
 	T_CustomPath,
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index f9db9ce..6a949fa 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -461,6 +461,7 @@ typedef struct RelOptInfo
 	PlannerInfo *subroot;		/* if subquery */
 	List	   *subplan_params; /* if subquery */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
+	Oid			fdwid;			/* if foreign table */
 	struct FdwRoutine *fdwroutine;		/* if foreign table */
 	Oid			fdw_server;		/* if foreign table */
 	void	   *fdw_private;	/* if foreign table */
@@ -1046,7 +1047,10 @@ typedef struct JoinPath
  * A nested-loop path needs no special fields.
  */
 
-typedef JoinPath NestPath;
+typedef struct NestPath
+{
+	JoinPath	jpath;
+} NestPath;
 
 /*
  * A mergejoin path has these fields.
@@ -1102,6 +1106,22 @@ typedef struct HashPath
 } HashPath;
 
 /*
+ * ForeignJoinPath represents a join between two relations consist of foreign
+ * table.
+ *
+ * fdw_private stores FDW private data about the join.  While fdw_private is
+ * not actually touched by the core code during normal operations, it's
+ * generally a good idea to use a representation that can be dumped by
+ * nodeToString(), so that you can examine the structure during debugging
+ * with tools like pprint().
+ */
+typedef struct ForeignJoinPath
+{
+	JoinPath	jpath;
+	List	   *fdw_private;
+} ForeignJoinPath;
+
+/*
  * Restriction clause info.
  *
  * We create one of these for each AND sub-clause of a restriction condition
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 26b17f5..7a1f236 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -124,6 +124,17 @@ extern HashPath *create_hashjoin_path(PlannerInfo *root,
 					 Relids required_outer,
 					 List *hashclauses);
 
+extern ForeignJoinPath *create_foreignjoin_path(PlannerInfo *root,
+					 RelOptInfo *joinrel,
+					 JoinType jointype,
+					 SpecialJoinInfo *sjinfo,
+					 SemiAntiJoinFactors *semifactors,
+					 Path *outer_path,
+					 Path *inner_path,
+					 List *restrict_clauses,
+					 List *pathkeys,
+					 Relids required_outer);
+
 extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 					Relids required_outer,
 					double loop_count);
