diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 59cb053..6795e5f 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 d76e739..0a645b6 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -48,7 +48,8 @@ PG_MODULE_MAGIC;
 
 /*
  * FDW-specific planner information kept in RelOptInfo.fdw_private for a
- * foreign table.  This information is collected by postgresGetForeignRelSize.
+ * foreign table or foreign join.  This information is collected by
+ * postgresGetForeignRelSize, or calculated from join source relations.
  */
 typedef struct PgFdwRelationInfo
 {
@@ -288,6 +289,22 @@ 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,
+						   JoinType jointype,
+						   SpecialJoinInfo *sjinfo,
+						   SemiAntiJoinFactors *semifactors,
+						   List *restrictlisti,
+						   Relids extra_lateral_rels);
+static ForeignScan *postgresGetForeignJoinPlan(PlannerInfo *root,
+							ForeignJoinPath *best_path,
+							List *tlist,
+							List *joinclauses,
+							List *otherclauses,
+							Plan *outer_plan,
+							Plan *inner_plan);
 
 /*
  * Helper functions
@@ -368,6 +385,10 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	/* Support functions for IMPORT FOREIGN SCHEMA */
 	routine->ImportForeignSchema = postgresImportForeignSchema;
 
+	/* Support functions for join push-down */
+	routine->GetForeignJoinPath = postgresGetForeignJoinPath;
+	routine->GetForeignJoinPlan = postgresGetForeignJoinPlan;
+
 	PG_RETURN_POINTER(routine);
 }
 
@@ -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
@@ -906,13 +932,23 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 	 * Identify which user to do the remote access as.  This should match what
 	 * ExecCheckRTEPerms() does.
 	 */
-	rte = rt_fetch(fsplan->scan.scanrelid, estate->es_range_table);
-	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+	if (fsplan->scan.scanrelid > 0)
+	{
+		rte = rt_fetch(fsplan->scan.scanrelid, estate->es_range_table);
+		userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+
+		fsstate->rel = node->ss.ss_currentRelation;
+		table = GetForeignTable(RelationGetRelid(fsstate->rel));
+		server = GetForeignServer(table->serverid);
+	}
+	else
+	{
+		/* XXX how can we determine userid to use for join cases? */
+		userid = GetCurrentRoleId();
+		server = GetForeignServer(16409);
+	}
 
 	/* Get info about foreign table. */
-	fsstate->rel = node->ss.ss_currentRelation;
-	table = GetForeignTable(RelationGetRelid(fsstate->rel));
-	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(userid, server->serverid);
 
 	/*
@@ -944,7 +980,16 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 											  ALLOCSET_SMALL_MAXSIZE);
 
 	/* Get info we'll need for input data conversion. */
-	fsstate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(fsstate->rel));
+	if (fsplan->scan.scanrelid > 0)
+		fsstate->attinmeta =
+			TupleDescGetAttInMetadata(RelationGetDescr(fsstate->rel));
+	else
+	{
+		TupleDesc	ps_tupdesc;
+
+		ps_tupdesc = ExecTypeFromTL(fsplan->fdw_ps_tlist, false);
+		fsstate->attinmeta = TupleDescGetAttInMetadata(ps_tupdesc);
+	}
 
 	/* Prepare for output conversion of parameters used in remote query. */
 	numParams = list_length(fsplan->fdw_exprs);
@@ -1725,10 +1770,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 +1790,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 +2887,233 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
 }
 
 /*
+ * Construct PgFdwRelationInfo from two join sources
+ */
+static PgFdwRelationInfo *
+merge_fpinfo(PgFdwRelationInfo *fpinfo_o, PgFdwRelationInfo *fpinfo_i)
+{
+	PgFdwRelationInfo *fpinfo;
+
+	fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
+	fpinfo->remote_conds =
+		list_concat(fpinfo_o->remote_conds, fpinfo_i->remote_conds);
+	fpinfo->local_conds =
+		list_concat(fpinfo_o->local_conds, fpinfo_i->local_conds);
+
+	fpinfo->attrs_used = NULL;		/* Use fdw_ps_tlist */
+	fpinfo->local_conds_cost.startup =
+		fpinfo_o->local_conds_cost.startup + fpinfo_i->local_conds_cost.startup;
+	fpinfo->local_conds_cost.per_tuple =
+		fpinfo_o->local_conds_cost.per_tuple + fpinfo_i->local_conds_cost.per_tuple;
+	fpinfo->local_conds_sel =
+		fpinfo_o->local_conds_sel * fpinfo_i->local_conds_sel;
+	/* XXX we should use join selectivity and join type */
+	fpinfo->rows = Min(fpinfo_o->rows, fpinfo_i->rows);
+	/* XXX we should consider only columns in fdw_ps_tlist */
+	fpinfo->width = fpinfo_o->width + fpinfo_i->width;
+	/* XXX we should estimate better costs */
+
+	fpinfo->use_remote_estimate = false;	/* Never use in join case */
+	fpinfo->fdw_startup_cost = fpinfo_o->fdw_startup_cost;
+	fpinfo->fdw_tuple_cost = fpinfo_o->fdw_tuple_cost;
+
+	fpinfo->startup_cost = fpinfo->fdw_startup_cost;
+	fpinfo->total_cost =
+		fpinfo->startup_cost + fpinfo->fdw_tuple_cost * fpinfo->rows;
+
+	fpinfo->table = NULL;	/* always NULL in join case */
+	fpinfo->server = fpinfo_o->server;
+	fpinfo->user = fpinfo_o->user ? fpinfo_o->user : fpinfo_i->user;
+
+	return fpinfo;
+}
+
+/*
+ * postgresGetForeignJoinPath
+ *		Add possible ForeignJoinPath to joinrel.
+ *
+ */
+static void
+postgresGetForeignJoinPath(PlannerInfo *root,
+						   RelOptInfo *joinrel,
+						   RelOptInfo *outerrel,
+						   RelOptInfo *innerrel,
+						   JoinType jointype,
+						   SpecialJoinInfo *sjinfo,
+						   SemiAntiJoinFactors *semifactors,
+						   List *restrictlist,
+						   Relids extra_lateral_rels)
+{
+	ForeignJoinPath *joinpath;
+	Path		   *path_o = outerrel->cheapest_total_path;
+	Path		   *path_i = innerrel->cheapest_total_path;
+	PgFdwRelationInfo *fpinfo_o;
+	PgFdwRelationInfo *fpinfo_i;
+	Relids			required_outer;
+
+	/* Skip considering reversed join combination */
+	elog(DEBUG1, "%s() outer: %d, inner: %d",
+		 __func__, outerrel->relid, innerrel->relid);
+	if (outerrel->relid < innerrel->relid)
+		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;
+
+	/*
+	 * Both relations in the join must belong to same server, and have same
+	 * checkAsUser to use one connection to execute SQL for the join.
+	 */
+	if (IsA(path_o, ForeignPath))
+		fpinfo_o = ((ForeignPath *) path_o)->path.parent->fdw_private;
+	else if (IsA(path_o, ForeignJoinPath))
+		fpinfo_o = ((ForeignJoinPath *) path_o)->jpath.path.parent->fdw_private;
+	else
+		fpinfo_o = NULL;
+	Assert(fpinfo_o);
+	if (IsA(path_i, ForeignPath))
+		fpinfo_i = ((ForeignPath *) path_i)->path.parent->fdw_private;
+	else if (IsA(path_i, ForeignJoinPath))
+		fpinfo_i = ((ForeignJoinPath *) path_i)->jpath.path.parent->fdw_private;
+	else
+		fpinfo_i = NULL;
+	Assert(fpinfo_i);
+
+	/* Servers should match */
+	if (fpinfo_o->server->serverid != fpinfo_i->server->serverid)
+		return;
+
+	/* Construct fpinfo for the join relation */
+	joinrel->fdw_private = merge_fpinfo(fpinfo_o, fpinfo_i);
+
+	/*
+	 * 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(path_o, path_i);
+	joinpath = create_foreignjoin_path(root,
+									   joinrel,
+									   jointype,
+									   sjinfo,
+									   semifactors,
+									   path_o,
+									   path_i,
+									   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 */
+}
+
+/*
+ * postgresGetForeignJoinPlan
+ *		Create ForeignJoin plan node from given ForeignJoinPath.
+ *
+ */
+static ForeignScan *
+postgresGetForeignJoinPlan(PlannerInfo *root,
+						   ForeignJoinPath *best_path,
+						   List *tlist,
+						   List *joinclauses,
+						   List *otherclauses,
+						   Plan *outer_plan,
+						   Plan *inner_plan)
+{
+	ForeignScan *join_plan;
+	List	   *params_list = NIL;
+	List	   *fdw_private = NIL;
+	List	   *retrieved_attrs = NIL;
+	Relids		relids;
+	StringInfoData sql;
+	ForeignPath *path_o;
+	ForeignPath *path_i;
+	List *retrieved_attrs_o = NIL;
+	List *retrieved_attrs_i = NIL;
+	PgFdwRelationInfo *fpinfo_o;
+	PgFdwRelationInfo *fpinfo_i;
+	PgFdwDeparseRel dr_o;
+	PgFdwDeparseRel dr_i;
+
+	/*
+	 * At the moment we support only joins between foreign tables.  This
+	 * limitation will be relaxed in future releases.
+	 */
+	Assert(IsA(outer_plan, ForeignScan));
+	Assert(IsA(inner_plan, ForeignScan));
+
+	/*
+	 * Retrieve Path and PgFdwRelationInfo of underlying ForeignScan to reuse
+	 * various information cumputed in ForeignScan planning.
+	 */
+	path_o = (ForeignPath *) best_path->jpath.outerjoinpath;
+	fpinfo_o = path_o->path.parent->fdw_private;
+	path_i = (ForeignPath *) best_path->jpath.innerjoinpath;
+	fpinfo_i = path_i->path.parent->fdw_private;
+
+	/*
+	 * Construcr deparse information for two relations.
+	 */
+	dr_o.baserel = path_o->path.parent;
+	dr_o.jointype = JOIN_INNER;
+	dr_o.joinclauses = NIL;
+	dr_o.attrs_used = fpinfo_o->attrs_used;
+	dr_o.retrieved_attrs = &retrieved_attrs_o;
+	dr_i.baserel = path_i->path.parent;
+	dr_i.jointype = best_path->jpath.jointype;
+	dr_i.joinclauses = joinclauses;
+	dr_i.attrs_used = fpinfo_i->attrs_used;
+	dr_i.retrieved_attrs = &retrieved_attrs_i;
+
+	relids = NULL;
+	relids = bms_add_member(relids, dr_o.baserel->relid);
+	relids = bms_add_member(relids, dr_i.baserel->relid);
+
+	initStringInfo(&sql);
+	deparseSelectSql(&sql, root, list_make2(&dr_o, &dr_i));
+	if (fpinfo_o->remote_conds)
+		appendWhereClause(&sql, root, relids, fpinfo_o->remote_conds, true,
+						  &params_list);
+	if (fpinfo_i->remote_conds)
+		appendWhereClause(&sql, root, relids, fpinfo_i->remote_conds,
+						  (fpinfo_o->remote_conds == NULL), &params_list);
+
+	/*
+	 * Different from ForeignScan, we store retrieved_attrs as a list of lists.
+	 * This allows subsequent processing to distinguish which relation is the
+	 * source.
+	 */
+	retrieved_attrs = list_make2(list_copy(*dr_o.retrieved_attrs),
+								 list_copy(*dr_i.retrieved_attrs));
+	fdw_private = list_make2(makeString(sql.data), retrieved_attrs);
+	elog(DEBUG1, "sql: %s", sql.data);
+	elog(DEBUG1, "retrieved_attrs: %s", nodeToString(retrieved_attrs));
+
+	join_plan = make_foreignscan(tlist,
+								 NIL,
+								 0,
+								 params_list,
+								 fdw_private);
+	return join_plan;
+}
+
+/*
  * 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 950c6f7..c71bf21 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 df69a95..d77eeea 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.
  */
@@ -310,12 +333,8 @@ static Oid
 GetFdwHandlerByRelId(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));
@@ -325,6 +344,16 @@ GetFdwHandlerByRelId(Oid relid)
 	serverid = tableform->ftserver;
 	ReleaseSysCache(tp);
 
+	return GetFdwRoutineByServerId(serverid);
+}
+
+FdwRoutine *
+GetFdwRoutineByServerId(Oid serverid)
+{
+	HeapTuple	tp;
+	Form_pg_foreign_server serverform;
+	Oid			fdwid;
+
 	/* Get foreign-data wrapper OID for the server. */
 	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
 	if (!HeapTupleIsValid(tp))
@@ -333,6 +362,16 @@ GetFdwHandlerByRelId(Oid relid)
 	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 c4a06fc..048db39 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1703,6 +1703,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");
@@ -1801,6 +1811,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node)
 	WRITE_NODE_FIELD(subplan);
 	WRITE_NODE_FIELD(subroot);
 	WRITE_NODE_FIELD(subplan_params);
+	WRITE_OID_FIELD(fdw_handler);
 	/* we don't try to print fdwroutine or fdw_private */
 	WRITE_NODE_FIELD(baserestrictinfo);
 	WRITE_NODE_FIELD(joininfo);
@@ -3125,6 +3136,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 020558b..a8506fc 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 5a24efa..04e59e6 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,
@@ -209,7 +209,29 @@ add_paths_to_joinrel(PlannerInfo *root,
 		extra_lateral_rels = NULL;
 
 	/*
-	 * 1. Consider mergejoin paths where both relations must be explicitly
+	 * 1. Consider foreignjoin paths when both outer and inner relations are
+	 * managed by same foreign-data wrapper, and share same server.  Besides it,
+	 * checkAsUser of all relations in the join must match.  These limitations
+	 * ensure that
+	 * This is done preceding to any local join consideration because
+	 * foreign join would be cheapst in most case when joining on remote side
+	 * is possible.
+	 */
+	if (joinrel->fdwroutine && joinrel->fdwroutine->GetForeignJoinPath)
+	{
+		joinrel->fdwroutine->GetForeignJoinPath(root,
+												joinrel,
+												outerrel,
+												innerrel,
+												jointype,
+												sjinfo,
+												&semifactors,
+												restrictlist,
+												extra_lateral_rels);
+	}
+
+	/*
+	 * 2. Consider mergejoin paths where both relations must be explicitly
 	 * sorted.  Skip this if we can't mergejoin.
 	 */
 	if (mergejoin_allowed)
@@ -219,7 +241,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 							 param_source_rels, extra_lateral_rels);
 
 	/*
-	 * 2. Consider paths where the outer relation need not be explicitly
+	 * 3. Consider paths where the outer relation need not be explicitly
 	 * sorted. This includes both nestloops and mergejoins where the outer
 	 * path is already ordered.  Again, skip this if we can't mergejoin.
 	 * (That's okay because we know that nestloop can't handle right/full
@@ -234,7 +256,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 #ifdef NOT_USED
 
 	/*
-	 * 3. Consider paths where the inner relation need not be explicitly
+	 * 4. Consider paths where the inner relation need not be explicitly
 	 * sorted.  This includes mergejoins only (nestloops were already built in
 	 * match_unsorted_outer).
 	 *
@@ -252,7 +274,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 #endif
 
 	/*
-	 * 4. Consider paths where both outer and inner relations must be hashed
+	 * 5. Consider paths where both outer and inner relations must be hashed
 	 * before being joined.  As above, disregard enable_hashjoin for full
 	 * joins, because there may be no other alternative.
 	 */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 06bea4d..d20fb50 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);
@@ -82,11 +83,14 @@ 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 ForeignScan *create_foreignjoin_plan(PlannerInfo *root,
+					 ForeignJoinPath *best_path, 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,
@@ -219,7 +223,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;
@@ -240,6 +244,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 		case T_CustomScan:
 			plan = create_scan_plan(root, best_path);
 			break;
+		case T_ForeignJoinPath:
 		case T_HashJoin:
 		case T_MergeJoin:
 		case T_NestLoop:
@@ -610,6 +615,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;
@@ -624,27 +630,41 @@ 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_ForeignJoinPath:
+			plan = (Plan *) create_foreignjoin_plan(root,
+												(ForeignJoinPath *) best_path,
+													tlist,
+													outer_plan,
+													inner_plan);
+			break;
 		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;
@@ -2114,12 +2134,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;
@@ -2133,7 +2153,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);
@@ -2146,7 +2166,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);
@@ -2158,7 +2178,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)
@@ -2195,9 +2215,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;
 }
@@ -2205,10 +2225,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;
@@ -2500,10 +2520,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;
@@ -2622,6 +2642,53 @@ create_hashjoin_plan(PlannerInfo *root,
 	return join_plan;
 }
 
+/*
+ * Unlike other join paths, ForeignJoinPath is transformed into ForiegnScan
+ * plan node.
+ */
+static ForeignScan *
+create_foreignjoin_plan(PlannerInfo *root,
+						ForeignJoinPath *best_path,
+						List *tlist,
+						Plan *outer_plan,
+						Plan *inner_plan)
+{
+	ForeignScan *join_plan;
+	List	   *joinrestrictclauses = best_path->jpath.joinrestrictinfo;
+	List	   *joinclauses;
+	List	   *otherclauses;
+
+	/* Sort join qual clauses into best execution order */
+	joinrestrictclauses = order_qual_clauses(root, joinrestrictclauses);
+
+	/* Get the join qual clauses (in plain expression form) */
+	/* Any pseudoconstant clauses are ignored here */
+	if (IS_OUTER_JOIN(best_path->jpath.jointype))
+	{
+		extract_actual_join_clauses(joinrestrictclauses,
+									&joinclauses, &otherclauses);
+	}
+	else
+	{
+		/* We can treat all clauses alike for an inner join */
+		joinclauses = extract_actual_clauses(joinrestrictclauses, false);
+		otherclauses = NIL;
+	}
+
+	/* Call FDW handler */
+	{
+		RelOptInfo *rel = best_path->jpath.path.parent;
+
+		Assert(rel->fdwroutine);
+		join_plan = rel->fdwroutine->GetForeignJoinPlan(root, best_path,
+														tlist, joinclauses,
+														otherclauses,
+														outer_plan, inner_plan);
+		join_plan->fdw_handler = rel->fdw_handler;
+	}
+
+	return join_plan;
+}
 
 /*****************************************************************************
  *
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 1395a21..d6434bf 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 a4a35c3..57763d4 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 ca71093..667ae1b 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -122,6 +122,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	rel->subplan = NULL;
 	rel->subroot = NULL;
 	rel->subplan_params = NIL;
+	rel->fdw_handler = InvalidOid;
 	rel->fdwroutine = NULL;
 	rel->fdw_private = NULL;
 	rel->baserestrictinfo = NIL;
@@ -384,7 +385,17 @@ build_join_rel(PlannerInfo *root,
 	joinrel->subplan = NULL;
 	joinrel->subroot = NULL;
 	joinrel->subplan_params = NIL;
-	joinrel->fdwroutine = NULL;
+	/* propagate common FDW information up to join relation */
+	if (inner_rel->fdw_handler == outer_rel->fdw_handler)
+	{
+		joinrel->fdwroutine = inner_rel->fdwroutine;
+		joinrel->fdw_handler = inner_rel->fdw_handler;
+	}
+	else
+	{
+		joinrel->fdw_handler = 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 b494ff2..b1f8532 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);
 
@@ -150,12 +168,19 @@ typedef struct FdwRoutine
 
 	/* Support functions for IMPORT FOREIGN SCHEMA */
 	ImportForeignSchema_function ImportForeignSchema;
+
+	/* Support functions for join push-down */
+	GetForeignJoinPath_function GetForeignJoinPath;
+	GetForeignJoinPlan_function GetForeignJoinPlan;
+
 } FdwRoutine;
 
 
 /* Functions in foreign/foreign.c */
 extern FdwRoutine *GetFdwRoutine(Oid fdwhandler);
 extern FdwRoutine *GetFdwRoutineByRelId(Oid relid);
+extern FdwRoutine * GetFdwRoutineByServerId(Oid serverid);
+extern FdwRoutine * GetFdwRoutineByFdwId(Oid fdwid);
 extern FdwRoutine *GetFdwRoutineForRelation(Relation relation, bool makecopy);
 extern Oid	GetFdwHandlerForRelation(Relation relation);
 extern bool IsImportableForeignTable(const char *tablename,
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 9c737b4..35acae7 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 97ef0fc..0f7a15d 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 9ef0b56..9914d1d 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1046,7 +1046,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 +1105,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 9923f0e..d4b6498 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);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index e66eaa5..082f7d7 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -41,7 +41,6 @@ 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,
