diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 4c49776..91a98fa 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -287,6 +287,15 @@ 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);
 
 /*
  * Helper functions
@@ -367,6 +376,13 @@ 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->BeginForeignJoin = NULL;
+	routine->ReScanForeignJoin = NULL;
+	routine->IterateForeignJoin = NULL;
+	routine->EndForeignJoin = NULL;
+
 	PG_RETURN_POINTER(routine);
 }
 
@@ -2832,6 +2848,49 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
 }
 
 /*
+ * 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)
+{
+	ListCell	   *lc;
+
+	foreach(lc, outerrel->pathlist)
+	{
+		Path		   *outerpath = (Path *) lfirst(lc);
+		Path		   *innerpath = innerrel->cheapest_total_path;
+		ForeignJoinPath *joinpath;
+		Relids			required_outer;
+
+		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 generate SQL for the join and store it into fdw_private. */
+		/* TODO determine cost and rows of the join. */
+		/* TODO add generated path into joinrel by add_path(). */
+	}
+}
+
+/*
  * Create a tuple from the specified row of the PGresult.
  *
  * rel is the local representation of the foreign table, attinmeta is
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 781a736..8559e3f 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -900,6 +900,10 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			pname = "Hash";		/* "Join" gets added by jointype switch */
 			sname = "Hash Join";
 			break;
+		case T_ForeignJoin:
+			pname = "Foreign";	/* "Join" gets added by jointype switch */
+			sname = "Foreign Join";
+			break;
 		case T_SeqScan:
 			pname = sname = "Seq Scan";
 			break;
@@ -1090,6 +1094,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
+		case T_ForeignJoin:
 			{
 				const char *jointype;
 
@@ -1390,6 +1395,18 @@ ExplainNode(PlanState *planstate, List *ancestors,
 				show_instrumentation_count("Rows Removed by Filter", 2,
 										   planstate, es);
 			break;
+		case T_ForeignJoin:
+			/* TODO: add FDW-specific EXPLAIN information */
+			show_upper_qual(((ForeignJoin *) plan)->join.joinqual,
+							"Join Filter", planstate, ancestors, es);
+			if (((ForeignJoin *) plan)->join.joinqual)
+				show_instrumentation_count("Rows Removed by Join Filter", 1,
+										   planstate, es);
+			show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
+			if (plan->qual)
+				show_instrumentation_count("Rows Removed by Filter", 2,
+										   planstate, es);
+			break;
 		case T_Agg:
 			show_agg_keys((AggState *) planstate, ancestors, es);
 			show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 6081b56..308cc99 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -24,6 +24,7 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
        nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
        nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
        nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
-       nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o spi.o
+       nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o spi.o \
+	   nodeForeignjoin.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 640964c..6b6d230 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -21,6 +21,7 @@
 #include "executor/nodeBitmapIndexscan.h"
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
+#include "executor/nodeForeignjoin.h"
 #include "executor/nodeForeignscan.h"
 #include "executor/nodeFunctionscan.h"
 #include "executor/nodeGroup.h"
@@ -209,6 +210,10 @@ ExecReScan(PlanState *node)
 			ExecReScanHashJoin((HashJoinState *) node);
 			break;
 
+		case T_ForeignJoinState:
+			ExecReScanForeignJoin((ForeignJoinState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecReScanMaterial((MaterialState *) node);
 			break;
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index c0189eb..e7d9a6e 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -85,6 +85,7 @@
 #include "executor/nodeBitmapIndexscan.h"
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
+#include "executor/nodeForeignjoin.h"
 #include "executor/nodeForeignscan.h"
 #include "executor/nodeFunctionscan.h"
 #include "executor/nodeGroup.h"
@@ -262,6 +263,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 													estate, eflags);
 			break;
 
+		case T_ForeignJoin:
+			result = (PlanState *) ExecInitForeignJoin((ForeignJoin *) node,
+													   estate, eflags);
+			break;
+
 			/*
 			 * materialization nodes
 			 */
@@ -457,6 +463,10 @@ ExecProcNode(PlanState *node)
 			result = ExecHashJoin((HashJoinState *) node);
 			break;
 
+		case T_ForeignJoinState:
+			result = ExecForeignJoin((ForeignJoinState *) node);
+			break;
+
 			/*
 			 * materialization nodes
 			 */
@@ -693,6 +703,10 @@ ExecEndNode(PlanState *node)
 			ExecEndHashJoin((HashJoinState *) node);
 			break;
 
+		case T_ForeignJoinState:
+			ExecEndForeignJoin((ForeignJoinState *) node);
+			break;
+
 			/*
 			 * materialization nodes
 			 */
diff --git a/src/backend/executor/nodeForeignjoin.c b/src/backend/executor/nodeForeignjoin.c
new file mode 100644
index 0000000..fbcdbc3
--- /dev/null
+++ b/src/backend/executor/nodeForeignjoin.c
@@ -0,0 +1,176 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeForeignjoin.c
+ *	  routines to support foreign joins
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/nodeForeignjoin.c
+ *
+ *-------------------------------------------------------------------------
+ */
+/*
+ *	 INTERFACE ROUTINES
+ *		ExecForeignJoin	    - process a foreign join of two foreign plans
+ *		ExecInitForeignJoin - initialize the join
+ *		ExecEndForeignJoin  - shut down the join
+ */
+
+#include "postgres.h"
+
+#include "executor/executor.h"
+#include "executor/nodeForeignjoin.h"
+#include "foreign/fdwapi.h"
+#include "utils/memutils.h"
+
+
+/* ----------------------------------------------------------------
+ *		ExecForeignJoin(node)
+ *
+ *		Returns the tuple joined from inner and outer tuples which
+ *		satisfies the qualification clause.
+ *
+ *		It delegates actual join processing to the foreign data wrapper
+ *		associsated with the foreign tables used in the join subtree.
+ *		Basically this node is very similar to ForeignScan except ForeignJoin
+ *		holds subplans of inner and outer relations.
+ *
+ *		NULL is returned if all the remaining tuples are consumed.
+ *
+ *		Basically this funcitons is very similar to ForeignNext called from
+ *		ExecForeignScan.
+ *
+ * ----------------------------------------------------------------
+ */
+TupleTableSlot *
+ExecForeignJoin(ForeignJoinState *node)
+{
+	TupleTableSlot *slot;
+	ExprContext *econtext = node->js.ps.ps_ExprContext;
+	MemoryContext oldcontext;
+
+	/* Call the Iterate function in short-lived context */
+	oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+	slot = node->fdwroutine->IterateForeignJoin(node);
+	MemoryContextSwitchTo(oldcontext);
+
+	/*
+	 * We don't force the slot into the "materialized" state, unlike
+	 * ExecForeignScan, because no system attribute is valid in joined result
+	 * tuple.
+	 */
+	return slot;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecInitForeignJoin
+ * ----------------------------------------------------------------
+ */
+ForeignJoinState *
+ExecInitForeignJoin(ForeignJoin *node, EState *estate, int eflags)
+{
+	ForeignJoinState *fjstate;
+	FdwRoutine *fdwroutine;
+
+	/* check for unsupported flags */
+	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
+
+	/*
+	 * create state structure
+	 */
+	fjstate = makeNode(ForeignJoinState);
+	fjstate->js.ps.plan = (Plan *) node;
+	fjstate->js.ps.state = estate;
+
+	/*
+	 * Miscellaneous initialization
+	 *
+	 * create expression context for node
+	 */
+	ExecAssignExprContext(estate, &fjstate->js.ps);
+
+	/*
+	 * initialize child expressions
+	 */
+	fjstate->js.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) node->join.plan.targetlist,
+					 (PlanState *) fjstate);
+	fjstate->js.ps.qual = (List *)
+		ExecInitExpr((Expr *) node->join.plan.qual,
+					 (PlanState *) fjstate);
+	fjstate->js.jointype = node->join.jointype;
+	fjstate->js.joinqual = (List *)
+		ExecInitExpr((Expr *) node->join.joinqual,
+					 (PlanState *) fjstate);
+
+	/*
+	 * tuple table initialization
+	 */
+	ExecInitResultTupleSlot(estate, &fjstate->js.ps);
+
+	/*
+	 * initialize tuple type and projection info
+	 */
+	ExecAssignResultTypeFromTL(&fjstate->js.ps);
+	ExecAssignProjectionInfo(&fjstate->js.ps, NULL);
+
+	/*
+	 * Acquire function pointers from the FDW's handler, and init fdw_state.
+	 */
+	fdwroutine = GetFdwRoutineByServerId(node->serverid);
+	fjstate->fdwroutine = fdwroutine;
+	fjstate->fdw_state = NULL;
+
+	/*
+	 * Tell the FDW to initiate the join.
+	 */
+	fdwroutine->BeginForeignJoin(fjstate, eflags);
+
+	/*
+	 * finally, wipe the current outer tuple clean.
+	 */
+	fjstate->js.ps.ps_TupFromTlist = false;
+
+	return fjstate;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecEndForeignJoin
+ *
+ *		closes down scans and frees allocated storage
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndForeignJoin(ForeignJoinState *node)
+{
+	/*
+	 * Free the exprcontext
+	 */
+	ExecFreeExprContext(&node->js.ps);
+
+	/*
+	 * clean out the tuple table
+	 */
+	ExecClearTuple(node->js.ps.ps_ResultTupleSlot);
+
+	/*
+	 * Tell the FDW that the join is done.
+	 */
+	node->fdwroutine->EndForeignJoin(node);
+}
+
+/* ----------------------------------------------------------------
+ *		ExecReScanForeignJoin
+ * ----------------------------------------------------------------
+ */
+void
+ExecReScanForeignJoin(ForeignJoinState *node)
+{
+	/*
+	 * Tell the FDW to rewind the join.
+	 */
+	node->fdwroutine->ReScanForeignJoin(node);
+}
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 4f5f6ae..9fd1069 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.
  */
@@ -311,12 +334,8 @@ FdwRoutine *
 GetFdwRoutineByRelId(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));
@@ -326,6 +345,18 @@ GetFdwRoutineByRelId(Oid relid)
 	serverid = tableform->ftserver;
 	ReleaseSysCache(tp);
 
+	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));
 	if (!HeapTupleIsValid(tp))
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 225756c..c8113ab 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -703,6 +703,27 @@ _copyHashJoin(const HashJoin *from)
 	return newnode;
 }
 
+/*
+ * _copyForeignJoin
+ */
+static ForeignJoin *
+_copyForeignJoin(const ForeignJoin *from)
+{
+	ForeignJoin *newnode = makeNode(ForeignJoin);
+
+	/*
+	 * copy node superclass fields
+	 */
+	CopyJoinFields((const Join *) from, (Join *) newnode);
+
+	/*
+	 * copy remainder of node
+	 */
+	COPY_NODE_FIELD(fdw_private);
+
+	return newnode;
+}
+
 
 /*
  * _copyMaterial
@@ -4054,6 +4075,9 @@ copyObject(const void *from)
 		case T_HashJoin:
 			retval = _copyHashJoin(from);
 			break;
+		case T_ForeignJoin:
+			retval = _copyForeignJoin(from);
+			break;
 		case T_Material:
 			retval = _copyMaterial(from);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1ff78eb..82c8624 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -623,6 +623,16 @@ _outHashJoin(StringInfo str, const HashJoin *node)
 }
 
 static void
+_outForeignJoin(StringInfo str, const ForeignJoin *node)
+{
+	WRITE_NODE_TYPE("FOREIGNJOIN");
+
+	_outJoinPlanInfo(str, (const Join *) node);
+
+	WRITE_NODE_FIELD(fdw_private);
+}
+
+static void
 _outAgg(StringInfo str, const Agg *node)
 {
 	int			i;
@@ -1668,6 +1678,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");
@@ -1766,6 +1786,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);
@@ -2864,6 +2885,9 @@ _outNode(StringInfo str, const void *obj)
 			case T_HashJoin:
 				_outHashJoin(str, obj);
 				break;
+			case T_ForeignJoin:
+				_outForeignJoin(str, obj);
+				break;
 			case T_Agg:
 				_outAgg(str, obj);
 				break;
@@ -3084,6 +3108,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/joinpath.c b/src/backend/optimizer/path/joinpath.c
index be54f3d..faadefc 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"
@@ -50,7 +51,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,
@@ -207,7 +207,26 @@ 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.  This is done preceding to any
+	 * local join consideration because foreignjoin 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)
@@ -217,7 +236,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
@@ -232,7 +251,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).
 	 *
@@ -250,7 +269,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 4b641a2..4c28dcb 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -83,6 +83,9 @@ static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
 					  Plan *outer_plan, Plan *inner_plan);
 static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path,
 					 Plan *outer_plan, Plan *inner_plan);
+static ForeignJoin *create_foreignjoin_plan(PlannerInfo *root,
+					 ForeignJoinPath *best_path, 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,
@@ -235,6 +238,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 		case T_ForeignScan:
 			plan = create_scan_plan(root, best_path);
 			break;
+		case T_ForeignJoin:
 		case T_HashJoin:
 		case T_MergeJoin:
 		case T_NestLoop:
@@ -625,6 +629,13 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path)
 												 outer_plan,
 												 inner_plan);
 			break;
+		case T_ForeignJoin:
+			/* Create ForeignScan plan node for ForeignJoin path */
+			plan = (Plan *) create_foreignjoin_plan(root,
+													(ForeignJoinPath *) best_path,
+													outer_plan,
+													inner_plan);
+			break;
 		case T_NestLoop:
 			/* Restore curOuterRels */
 			bms_free(root->curOuterRels);
@@ -2524,6 +2535,28 @@ create_hashjoin_plan(PlannerInfo *root,
 	return join_plan;
 }
 
+static ForeignJoin *
+create_foreignjoin_plan(PlannerInfo *root,
+						ForeignJoinPath *best_path,
+						Plan *outer_plan,
+						Plan *inner_plan)
+{
+	ForeignJoin *join_plan;
+	RelOptInfo *rel = best_path->jpath.path.parent;
+
+	Assert(rel->fdwroutine);
+	Assert(rel->fdwroutine->GetForeignJoinPlan);
+
+	join_plan = rel->fdwroutine->GetForeignJoinPlan(root,
+													best_path,
+													outer_plan,
+													inner_plan);
+
+	copy_path_costsize(&join_plan->join.plan, &best_path->jpath.path);
+
+	return join_plan;
+}
+
 
 /*****************************************************************************
  *
@@ -3727,6 +3760,34 @@ make_mergejoin(List *tlist,
 	return node;
 }
 
+ForeignJoin *
+make_foreignjoin(List *tlist,
+				 List *joinclauses,
+				 List *otherclauses,
+				 Oid serverid,
+				 List *fdwclauses,
+				 List *fdw_private,
+				 Plan *lefttree,
+				 Plan *righttree,
+				 JoinType jointype)
+{
+	ForeignJoin *node = makeNode(ForeignJoin);
+	Plan	   *plan = &node->join.plan;
+
+	/* cost will be filled in by create_foreignjoin_plan */
+	plan->targetlist = tlist;
+	plan->qual = otherclauses;
+	plan->lefttree = lefttree;
+	plan->righttree = righttree;
+	node->join.jointype = jointype;
+	node->join.joinqual = joinclauses;
+	node->serverid = serverid;
+	node->fdwclauses = fdwclauses;
+	node->fdw_private = fdw_private;
+
+	return node;
+}
+
 /*
  * make_sort --- basic routine to build a Sort plan node
  *
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 9ddc8ad..a3501706 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -582,6 +582,7 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
+		case T_ForeignJoin:
 			set_join_references(root, (Join *) plan, rtoffset);
 			break;
 
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 3e7dc85..1f4e361 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2403,6 +2403,15 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 							  &context);
 			break;
 
+		case T_ForeignJoin:
+			/*
+			 * TODO: consider ForeignJoin-specific information, see
+			 * T_ForeignScan section above.
+			 */
+			finalize_primnode((Node *) ((Join *) plan)->joinqual,
+							  &context);
+			break;
+
 		case T_Limit:
 			finalize_primnode(((Limit *) plan)->limitOffset,
 							  &context);
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 319e8b2..4e9fb44 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -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_ForeignJoin;
+	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 b2becfa..22b7523 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"
@@ -378,9 +379,15 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 	/* Grab the fdwroutine info using the relcache, while we have it */
 	if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		rel->serverid = GetForeignTableServerOid(relation->rd_id);
 		rel->fdwroutine = GetFdwRoutineForRelation(relation, true);
+	}
 	else
+	{
+		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 4c76f54..278ca2e 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->serverid = 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->serverid == outer_rel->serverid)
+	{
+		joinrel->fdwroutine = inner_rel->fdwroutine;
+		joinrel->serverid = inner_rel->serverid;
+	}
+	else
+	{
+		joinrel->serverid = InvalidOid;
+		joinrel->fdwroutine = NULL;
+	}
 	joinrel->fdw_private = NULL;
 	joinrel->baserestrictinfo = NIL;
 	joinrel->baserestrictcost.startup = 0;
diff --git a/src/include/executor/nodeForeignjoin.h b/src/include/executor/nodeForeignjoin.h
new file mode 100644
index 0000000..802498c
--- /dev/null
+++ b/src/include/executor/nodeForeignjoin.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeForeignjoin.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/nodeForeignjoin.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODEFOREIGNJOIN_H
+#define NODEFOREIGNJOIN_H
+
+#include "nodes/execnodes.h"
+
+extern ForeignJoinState *ExecInitForeignJoin(ForeignJoin *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecForeignJoin(ForeignJoinState *node);
+extern void ExecEndForeignJoin(ForeignJoinState *node);
+extern void ExecReScanForeignJoin(ForeignJoinState *node);
+
+#endif   /* NODEFOREIGNJOIN_H */
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index dc0a7fc7..56fc8e0 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -82,6 +82,29 @@ 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 ForeignJoin *(*GetForeignJoinPlan_function) (PlannerInfo *root,
+													 ForeignJoinPath *best_path,
+													 Plan *outerplan,
+													 Plan *innerplan);
+
+typedef void (*BeginForeignJoin_function) (ForeignJoinState *node,
+													   int eflags);
+typedef TupleTableSlot *(*IterateForeignJoin_function) (ForeignJoinState *node);
+
+typedef void (*ReScanForeignJoin_function) (ForeignJoinState *node);
+
+typedef void (*EndForeignJoin_function) (ForeignJoinState *node);
+
 typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
 													struct ExplainState *es);
 
@@ -150,12 +173,22 @@ typedef struct FdwRoutine
 
 	/* Support functions for IMPORT FOREIGN SCHEMA */
 	ImportForeignSchema_function ImportForeignSchema;
+
+	/* Support functions for join push-down */
+	GetForeignJoinPath_function GetForeignJoinPath;
+	GetForeignJoinPlan_function GetForeignJoinPlan;
+	BeginForeignJoin_function BeginForeignJoin;
+	IterateForeignJoin_function IterateForeignJoin;
+	ReScanForeignJoin_function ReScanForeignJoin;
+	EndForeignJoin_function EndForeignJoin;
+
 } FdwRoutine;
 
 
 /* Functions in foreign/foreign.c */
 extern FdwRoutine *GetFdwRoutine(Oid fdwhandler);
 extern FdwRoutine *GetFdwRoutineByRelId(Oid relid);
+extern FdwRoutine * GetFdwRoutineByServerId(Oid serverid);
 extern FdwRoutine *GetFdwRoutineForRelation(Relation relation, bool makecopy);
 extern bool IsImportableForeignTable(const char *tablename,
 						 ImportForeignSchemaStmt *stmt);
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/execnodes.h b/src/include/nodes/execnodes.h
index b271f21..dd2f69c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1636,6 +1636,21 @@ typedef struct HashJoinState
 	bool		hj_OuterNotEmpty;
 } HashJoinState;
 
+/* ----------------
+ *	 ForeignJoinState information
+ *
+ *		fdwroutine				handler functions used to process the join
+ *		fdw_state				FDW-private state information
+ * ----------------
+ */
+typedef struct ForeignJoinState
+{
+	JoinState	js;				/* its first field is NodeTag */
+	/* use struct pointer to avoid including fdwapi.h here */
+	struct FdwRoutine *fdwroutine;
+	void	   *fdw_state;		/* foreign-data wrapper can keep state here */
+} ForeignJoinState;
+
 
 /* ----------------------------------------------------------------
  *				 Materialization State Information
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 154d943..c81eff3 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -66,6 +66,7 @@ typedef enum NodeTag
 	T_NestLoop,
 	T_MergeJoin,
 	T_HashJoin,
+	T_ForeignJoin,
 	T_Material,
 	T_Sort,
 	T_Group,
@@ -111,6 +112,7 @@ typedef enum NodeTag
 	T_NestLoopState,
 	T_MergeJoinState,
 	T_HashJoinState,
+	T_ForeignJoinState,
 	T_MaterialState,
 	T_SortState,
 	T_GroupState,
@@ -222,6 +224,7 @@ typedef enum NodeTag
 	T_NestPath,
 	T_MergePath,
 	T_HashPath,
+	T_ForeignJoinPath,
 	T_TidPath,
 	T_ForeignPath,
 	T_AppendPath,
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 1839494..bb741ae 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -569,6 +569,18 @@ typedef struct HashJoin
 } HashJoin;
 
 /* ----------------
+ *		foreign join node
+ * ----------------
+ */
+typedef struct ForeignJoin
+{
+	Join		join;
+	Oid			serverid;
+	List	   *fdwclauses;		/* expressions that FDW may evaluate */
+	List	   *fdw_private;
+} ForeignJoin;
+
+/* ----------------
  *		materialization node
  * ----------------
  */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index f1a0504..71ac248 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -364,13 +364,14 @@ 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
+ *		serverid - OID of 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)
  *
  *		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.  Likewise, serverid,
+ *		fdwroutine, and fdw_private are filled during initial path creation.
  *
  *		For otherrels that are appendrel members, these fields are filled
  *		in just as for a baserel.
@@ -459,6 +460,7 @@ typedef struct RelOptInfo
 	PlannerInfo *subroot;		/* if subquery */
 	List	   *subplan_params; /* if subquery */
 	/* use "struct FdwRoutine" to avoid including fdwapi.h here */
+	Oid			serverid;		/* if foreign table */
 	struct FdwRoutine *fdwroutine;		/* if foreign table */
 	void	   *fdw_private;	/* if foreign table */
 
@@ -1053,6 +1055,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);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 3fdc2cb..a0e5788 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -45,6 +45,10 @@ extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual,
 				  Index scanrelid, Plan *subplan);
 extern ForeignScan *make_foreignscan(List *qptlist, List *qpqual,
 				 Index scanrelid, List *fdw_exprs, List *fdw_private);
+extern ForeignJoin *make_foreignjoin(List *tlist, List *joinclauses,
+				 List *otherclauses, Oid serverid, List *fdwclauses,
+				 List *fdw_private, Plan *lefttree, Plan *righttree,
+				 JoinType jointype);
 extern Append *make_append(List *appendplans, List *tlist);
 extern RecursiveUnion *make_recursive_union(List *tlist,
 					 Plan *lefttree, Plan *righttree, int wtParam,
