join_pushdown_v1.patch

text/plain

Filename: join_pushdown_v1.patch
Type: text/plain
Part: 0
Message: WIP: Join push-down for foreign tables

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
Series: patch v1
File+
doc/src/sgml/config.sgml 0 0
doc/src/sgml/ref/postgres-ref.sgml 0 0
src/backend/commands/explain.c 0 0
src/backend/executor/execUtils.c 0 0
src/backend/executor/nodeForeignscan.c 0 0
src/backend/nodes/copyfuncs.c 0 0
src/backend/nodes/outfuncs.c 0 0
src/backend/optimizer/path/allpaths.c 0 0
src/backend/optimizer/path/costsize.c 0 0
src/backend/optimizer/path/joinpath.c 0 0
src/backend/optimizer/plan/createplan.c 0 0
src/backend/optimizer/README 0 0
src/backend/optimizer/util/pathnode.c 0 0
src/backend/optimizer/util/plancat.c 0 0
src/backend/optimizer/util/relnode.c 0 0
src/backend/tcop/postgres.c 0 0
src/backend/utils/misc/guc.c 0 0
src/backend/utils/misc/postgresql.conf.sample 0 0
src/include/executor/executor.h 0 0
src/include/foreign/fdwapi.h 0 0
src/include/nodes/nodes.h 0 0
src/include/nodes/plannodes.h 0 0
src/include/nodes/relation.h 0 0
src/include/optimizer/cost.h 0 0
src/include/optimizer/pathnode.h 0 0
src/test/regress/expected/rangefuncs.out 0 0
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f95f9cc..70d25ae 100644
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
*************** SET ENABLE_SEQSCAN TO OFF;
*** 2360,2365 ****
--- 2360,2378 ----
        </listitem>
       </varlistentry>
  
+      <varlistentry id="guc-enable-foreignjoin" xreflabel="enable_foreignjoin">
+       <term><varname>enable_foreignjoin</varname> (<type>boolean</type>)</term>
+       <indexterm>
+        <primary><varname>enable_foreignjoin</> configuration parameter</primary>
+       </indexterm>
+       <listitem>
+        <para>
+         Enables or disables the query planner's use of foreign-join plan
+         types. The default is <literal>on</>.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
       <varlistentry id="guc-enable-hashagg" xreflabel="enable_hashagg">
        <term><varname>enable_hashagg</varname> (<type>boolean</type>)</term>
        <indexterm>
diff --git a/doc/src/sgml/ref/postgres-ref.sgml b/doc/src/sgml/ref/postgres-ref.sgml
index b16bbdf..bebaebe 100644
*** a/doc/src/sgml/ref/postgres-ref.sgml
--- b/doc/src/sgml/ref/postgres-ref.sgml
*************** PostgreSQL documentation
*** 362,375 ****
  
      <variablelist>
       <varlistentry>
!       <term><option>-f</option> <literal>{ s | i | m | n | h }</literal></term>
        <listitem>
         <para>
          Forbids the use of particular scan and join methods:
          <literal>s</literal> and <literal>i</literal>
          disable sequential and index scans respectively, while
!         <literal>n</literal>, <literal>m</literal>, and <literal>h</literal>
!         disable nested-loop, merge and hash joins respectively.
         </para>
  
         <para>
--- 362,376 ----
  
      <variablelist>
       <varlistentry>
!       <term><option>-f</option> <literal>{ s | i | m | n | h | f }</literal></term>
        <listitem>
         <para>
          Forbids the use of particular scan and join methods:
          <literal>s</literal> and <literal>i</literal>
          disable sequential and index scans respectively, while
!         <literal>n</literal>, <literal>m</literal>, <literal>h</literal>, and
!         <literal>f</literal>
!         disable nested-loop, merge, hash and foreign joins respectively.
         </para>
  
         <para>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 6408d16..1bd035d 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static void show_hash_info(HashState *ha
*** 79,84 ****
--- 79,85 ----
  static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
  static const char *explain_get_index_name(Oid indexId);
  static void ExplainScanTarget(Scan *plan, ExplainState *es);
+ static void ExplainForeignScanTarget(Scan *plan, ExplainState *es);
  static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
  static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
  static void ExplainMemberNodes(List *plans, PlanState **planstates,
*************** ExplainNode(PlanState *planstate, List *
*** 833,841 ****
  		case T_ValuesScan:
  		case T_CteScan:
  		case T_WorkTableScan:
- 		case T_ForeignScan:
  			ExplainScanTarget((Scan *) plan, es);
  			break;
  		case T_BitmapIndexScan:
  			{
  				BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan;
--- 834,844 ----
  		case T_ValuesScan:
  		case T_CteScan:
  		case T_WorkTableScan:
  			ExplainScanTarget((Scan *) plan, es);
  			break;
+ 		case T_ForeignScan:
+ 			ExplainForeignScanTarget((ForeignScan *) plan, es);
+ 			break;
  		case T_BitmapIndexScan:
  			{
  				BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan;
*************** ExplainScanTarget(Scan *plan, ExplainSta
*** 1556,1561 ****
--- 1559,1584 ----
  }
  
  /*
+  * Show the target of a ForeignScan node
+  */
+ static void
+ ExplainForeignScanTarget(Scan *plan, ExplainState *es)
+ {
+ 	Assert(IsA(plan, ForeignScan));
+ 
+ 	/*
+ 	 * If scan target is an foreign table, show in normal scan format,
+ 	 * otherwise, show in specific format.
+ 	 */
+ 	if (plan->scanrelid > 0)
+ 		ExplainTargetRel((Plan *) plan, plan->scanrelid, es);
+ 	else
+ 	{
+ 		appendStringInfo(es->str, " on multiple foreign tables");
+ 	}
+ }
+ 
+ /*
   * Show the target of a ModifyTable node
   */
  static void
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 4dbf10b..351b746 100644
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
*************** ExecAssignScanTypeFromOuterPlan(ScanStat
*** 756,761 ****
--- 756,786 ----
  	ExecAssignScanType(scanstate, tupDesc);
  }
  
+ /* ----------------
+  *		ExecAssignScanTypeFromTL
+  * ----------------
+  */
+ void
+ ExecAssignScanTypeFromTL(ScanState *scanstate)
+ {
+ 	bool			hasoid;
+ 	TupleTableSlot *slot = scanstate->ss_ScanTupleSlot;
+ 	TupleDesc		tupDesc;
+ 
+ 	if (ExecContextForcesOids(&scanstate->ps, &hasoid))
+ 	{
+ 		/* context forces OID choice; hasoid is now set correctly */
+ 	}
+ 	else
+ 	{
+ 		/* given free choice, don't leave space for OIDs in result tuples */
+ 		hasoid = false;
+ 	}
+ 
+ 	tupDesc = ExecTypeFromTL(scanstate->ps.plan->targetlist, hasoid);
+ 	ExecSetSlotDescriptor(slot, tupDesc);
+ }
+ 
  
  /* ----------------------------------------------------------------
   *				  Scan node support
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 841ae69..b13f0dd 100644
*** a/src/backend/executor/nodeForeignscan.c
--- b/src/backend/executor/nodeForeignscan.c
***************
*** 25,30 ****
--- 25,31 ----
  #include "executor/executor.h"
  #include "executor/nodeForeignscan.h"
  #include "foreign/fdwapi.h"
+ #include "foreign/foreign.h"
  #include "utils/rel.h"
  
  static TupleTableSlot *ForeignNext(ForeignScanState *node);
*************** ExecForeignScan(ForeignScanState *node)
*** 101,109 ****
  ForeignScanState *
  ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
  {
! 	ForeignScanState *scanstate;
! 	Relation	currentRelation;
! 	FdwRoutine *fdwroutine;
  
  	/* check for unsupported flags */
  	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
--- 102,112 ----
  ForeignScanState *
  ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
  {
! 	ForeignScanState   *scanstate;
! 	Relation			currentRelation;
! 	ForeignServer	   *server;
! 	ForeignDataWrapper *wrapper;
! 	FdwRoutine		   *fdwroutine;
  
  	/* check for unsupported flags */
  	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
*************** ExecInitForeignScan(ForeignScan *node, E
*** 140,166 ****
  	ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
  	ExecInitScanTupleSlot(estate, &scanstate->ss);
  
! 	/*
! 	 * open the base relation and acquire appropriate lock on it.
! 	 */
! 	currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid);
! 	scanstate->ss.ss_currentRelation = currentRelation;
  
! 	/*
! 	 * get the scan type from the relation descriptor.
! 	 */
! 	ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
  
! 	/*
! 	 * Initialize result tuple type and projection info.
! 	 */
! 	ExecAssignResultTypeFromTL(&scanstate->ss.ps);
! 	ExecAssignScanProjectionInfo(&scanstate->ss);
  
  	/*
  	 * Acquire function pointers from the FDW's handler, and init fdw_state.
  	 */
! 	fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(currentRelation));
  	scanstate->fdwroutine = fdwroutine;
  	scanstate->fdw_state = NULL;
  
--- 143,193 ----
  	ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
  	ExecInitScanTupleSlot(estate, &scanstate->ss);
  
! 	if (node->scan.scanrelid != InvalidOid)
! 	{
! 		/*
! 		 * open the base relation and acquire appropriate lock on it.
! 		 */
! 		currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid);
! 		scanstate->ss.ss_currentRelation = currentRelation;
  
! 		/*
! 		 * get the scan type from the relation descriptor.
! 		 */
! 		ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
  
! 		/*
! 		 * Initialize result tuple type and projection info.
! 		 */
! 		ExecAssignResultTypeFromTL(&scanstate->ss.ps);
! 		ExecAssignScanProjectionInfo(&scanstate->ss);
! 	}
! 	else
! 	{
! 		TupleDesc		tupleDesc;
! 
! 		/* TODO: open related relations and acquire appropriate lock on them. */
! 		scanstate->ss.ss_currentRelation = NULL;
! 
! 		/*
! 		 * get the scan type from the target list.
! 		 */
! 		ExecAssignScanTypeFromTL(&scanstate->ss);
! 		tupleDesc = scanstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
! 
! 		/*
! 		 * Initialize result tuple type and projection info.
! 		 */
! 		ExecAssignResultTypeFromTL(&scanstate->ss.ps);
! 		ExecAssignProjectionInfo(&scanstate->ss.ps, NULL);
! 	}
  
  	/*
  	 * Acquire function pointers from the FDW's handler, and init fdw_state.
  	 */
! 	server = GetForeignServer(node->serverid);
! 	wrapper = GetForeignDataWrapper(server->fdwid);
! 	fdwroutine = GetFdwRoutine(wrapper->fdwhandler);
  	scanstate->fdwroutine = fdwroutine;
  	scanstate->fdw_state = NULL;
  
*************** ExecEndForeignScan(ForeignScanState *nod
*** 192,198 ****
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  
  	/* close the relation. */
! 	ExecCloseScanRelation(node->ss.ss_currentRelation);
  }
  
  /* ----------------------------------------------------------------
--- 219,226 ----
  	ExecClearTuple(node->ss.ss_ScanTupleSlot);
  
  	/* close the relation. */
! 	if (node->ss.ss_currentRelation != NULL)
! 		ExecCloseScanRelation(node->ss.ss_currentRelation);
  }
  
  /* ----------------------------------------------------------------
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 661a516..30f3d36 100644
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyForeignScan(ForeignScan *from)
*** 565,570 ****
--- 565,571 ----
  	/*
  	 * copy remainder of node
  	 */
+ 	COPY_SCALAR_FIELD(serverid);
  	COPY_SCALAR_FIELD(fsSystemCol);
  	COPY_NODE_FIELD(fdwplan);
  
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0d0ce3c..f0532be 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outForeignScan(StringInfo str, ForeignS
*** 543,548 ****
--- 543,549 ----
  
  	_outScanInfo(str, (Scan *) node);
  
+ 	WRITE_OID_FIELD(serverid);
  	WRITE_BOOL_FIELD(fsSystemCol);
  	WRITE_NODE_FIELD(fdwplan);
  }
*************** _outHashPath(StringInfo str, HashPath *n
*** 1647,1652 ****
--- 1648,1663 ----
  }
  
  static void
+ _outForeignJoinPath(StringInfo str, ForeignJoinPath *node)
+ {
+ 	WRITE_NODE_TYPE("FOREIGNJOINPATH");
+ 
+ 	_outJoinPathInfo(str, (JoinPath *) node);
+ 
+ 	WRITE_NODE_FIELD(fdwplan);
+ }
+ 
+ static void
  _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
  {
  	WRITE_NODE_TYPE("PLANNERGLOBAL");
*************** _outRelOptInfo(StringInfo str, RelOptInf
*** 1734,1739 ****
--- 1745,1751 ----
  	WRITE_NODE_FIELD(baserestrictinfo);
  	WRITE_NODE_FIELD(joininfo);
  	WRITE_BOOL_FIELD(has_eclass_joins);
+ 	WRITE_OID_FIELD(serverid);
  	WRITE_BITMAPSET_FIELD(index_outer_relids);
  	WRITE_NODE_FIELD(index_inner_paths);
  }
*************** _outNode(StringInfo str, void *obj)
*** 2967,2972 ****
--- 2979,2987 ----
  			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/README b/src/backend/optimizer/README
index aaa754c..f06cb27 100644
*** a/src/backend/optimizer/README
--- b/src/backend/optimizer/README
*************** RelOptInfo      - a relation or joined r
*** 356,361 ****
--- 356,362 ----
    NestPath      - nested-loop joins
    MergePath     - merge joins
    HashPath      - hash joins
+   ForeignJoinPath - foreign joins
  
   EquivalenceClass - a data structure representing a set of values known equal
  
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index b421481..13f7165 100644
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** print_path(PlannerInfo *root, Path *path
*** 1590,1595 ****
--- 1590,1599 ----
  			ptype = "HashJoin";
  			join = true;
  			break;
+ 		case T_ForeignJoinPath:
+ 			ptype = "ForeignJoin";
+ 			join = true;
+ 			break;
  		default:
  			ptype = "???Path";
  			break;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 7812a86..e843ffd 100644
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
*************** bool		enable_nestloop = true;
*** 118,123 ****
--- 118,124 ----
  bool		enable_material = true;
  bool		enable_mergejoin = true;
  bool		enable_hashjoin = true;
+ bool		enable_foreignjoin = true;
  
  typedef struct
  {
*************** cost_mergejoin(MergePath *path, PlannerI
*** 2071,2076 ****
--- 2072,2082 ----
  }
  
  /*
+  * cost_foreignjoin() is not defined here because the costs of a foreign join
+  * is estimated by each FDW via PlanForeignJoin.
+  */
+ 
+ /*
   * run mergejoinscansel() with caching
   */
  static MergeScanSelCache *
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 7d3cf42..3024565 100644
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** match_unsorted_outer(PlannerInfo *root,
*** 541,546 ****
--- 541,566 ----
  											  merge_pathkeys));
  		}
  
+ 		if (enable_foreignjoin &&
+ 			joinrel->serverid != InvalidOid &&
+ 			(IsA(outerpath, ForeignPath) || IsA(outerpath, ForeignJoinPath)) &&
+ 			(IsA(inner_cheapest_total, ForeignPath) ||
+ 			 IsA(inner_cheapest_total, ForeignJoinPath)))
+ 
+ 		{
+ 			ForeignJoinPath	   *path;
+ 			path = create_foreignjoin_path(root,
+ 										   joinrel,
+ 										   jointype,
+ 										   sjinfo,
+ 										   outerpath,
+ 										   inner_cheapest_total,
+ 										   restrictlist,
+ 										   merge_pathkeys);
+ 			if (path != NULL)
+ 				add_path(joinrel, (Path *) path);
+ 		}
+ 
  		/* Can't do anything else if outer path needs to be unique'd */
  		if (save_jointype == JOIN_UNIQUE_OUTER)
  			continue;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index b674afe..ccca59c 100644
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** static WorkTableScan *create_worktablesc
*** 74,79 ****
--- 74,81 ----
  						  List *tlist, List *scan_clauses);
  static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
  						List *tlist, List *scan_clauses);
+ static ForeignScan *create_foreignjoin_plan(PlannerInfo *root, ForeignJoinPath *best_path,
+ 						List *tlist, List *scan_clauses);
  static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path,
  					 Plan *outer_plan, Plan *inner_plan);
  static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
*************** static CteScan *make_ctescan(List *qptli
*** 117,123 ****
  static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
  				   Index scanrelid, int wtParam);
  static ForeignScan *make_foreignscan(List *qptlist, List *qpqual,
! 				 Index scanrelid, bool fsSystemCol, FdwPlan *fdwplan);
  static BitmapAnd *make_bitmap_and(List *bitmapplans);
  static BitmapOr *make_bitmap_or(List *bitmapplans);
  static NestLoop *make_nestloop(List *tlist,
--- 119,126 ----
  static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
  				   Index scanrelid, int wtParam);
  static ForeignScan *make_foreignscan(List *qptlist, List *qpqual,
! 				 Index scanrelid, Oid serverid, bool fsSystemCol,
! 				 FdwPlan *fdwplan);
  static BitmapAnd *make_bitmap_and(List *bitmapplans);
  static BitmapOr *make_bitmap_or(List *bitmapplans);
  static NestLoop *make_nestloop(List *tlist,
*************** create_plan_recurse(PlannerInfo *root, P
*** 214,219 ****
--- 217,223 ----
  		case T_CteScan:
  		case T_WorkTableScan:
  		case T_ForeignScan:
+ 		case T_ForeignJoin:		/* ForeignJoinPath become a ForeignScan */
  			plan = create_scan_plan(root, best_path);
  			break;
  		case T_HashJoin:
*************** create_scan_plan(PlannerInfo *root, Path
*** 361,366 ****
--- 365,377 ----
  													scan_clauses);
  			break;
  
+ 		case T_ForeignJoin:
+ 			plan = (Plan *) create_foreignjoin_plan(root,
+ 													(ForeignJoinPath *) best_path,
+ 													tlist,
+ 													scan_clauses);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized node type: %d",
  				 (int) best_path->pathtype);
*************** create_foreignscan_plan(PlannerInfo *roo
*** 1813,1818 ****
--- 1824,1830 ----
  	scan_plan = make_foreignscan(tlist,
  								 scan_clauses,
  								 scan_relid,
+ 								 rel->serverid,
  								 fsSystemCol,
  								 best_path->fdwplan);
  
*************** create_foreignscan_plan(PlannerInfo *roo
*** 1821,1826 ****
--- 1833,1884 ----
  	return scan_plan;
  }
  
+ /*
+  * create_foreignjoin_plan
+  *	 Returns a foreignscan plan for the join relation joined by 'best_path'
+  *	 with restriction clauses 'scan_clauses' and targetlist 'tlist'.
+  */
+ static ForeignScan *
+ create_foreignjoin_plan(PlannerInfo *root, ForeignJoinPath *best_path,
+ 						List *tlist, List *scan_clauses)
+ {
+ 	ForeignScan *scan_plan;
+ 	RelOptInfo *rel = best_path->jpath.path.parent;
+ 	Index		scan_relid = rel->relid;
+ 	bool		fsSystemCol;
+ 	int			i;
+ 
+ 	/* Sort clauses into best execution order */
+ 	scan_clauses = order_qual_clauses(root, scan_clauses);
+ 
+ 	/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ 	scan_clauses = extract_actual_clauses(scan_clauses, false);
+ 
+ 	fsSystemCol = false;
+ #ifdef NOT_USED
+ 	/* Detect whether any system columns are requested from rel */
+ 	for (i = rel->min_attr; i < 0; i++)
+ 	{
+ 		if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+ 		{
+ 			fsSystemCol = true;
+ 			break;
+ 		}
+ 	}
+ #endif
+ 
+ 	scan_plan = make_foreignscan(tlist,
+ 								 scan_clauses,
+ 								 scan_relid,
+ 								 rel->serverid,
+ 								 fsSystemCol,
+ 								 best_path->fdwplan);
+ 
+ 	copy_path_costsize(&scan_plan->scan.plan, &best_path->jpath.path);
+ 
+ 	return scan_plan;
+ }
+ 
  
  /*****************************************************************************
   *
*************** static ForeignScan *
*** 3046,3051 ****
--- 3104,3110 ----
  make_foreignscan(List *qptlist,
  				 List *qpqual,
  				 Index scanrelid,
+ 				 Oid serverid,
  				 bool fsSystemCol,
  				 FdwPlan *fdwplan)
  {
*************** make_foreignscan(List *qptlist,
*** 3058,3063 ****
--- 3117,3123 ----
  	plan->lefttree = NULL;
  	plan->righttree = NULL;
  	node->scan.scanrelid = scanrelid;
+ 	node->serverid = serverid;
  	node->fsSystemCol = fsSystemCol;
  	node->fdwplan = fdwplan;
  
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 4a1c94a..b2e2a7a 100644
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
***************
*** 17,22 ****
--- 17,23 ----
  #include <math.h>
  
  #include "foreign/fdwapi.h"
+ #include "foreign/foreign.h"
  #include "miscadmin.h"
  #include "nodes/nodeFuncs.h"
  #include "optimizer/clauses.h"
*************** create_hashjoin_path(PlannerInfo *root,
*** 1603,1605 ****
--- 1604,1686 ----
  
  	return pathnode;
  }
+ 
+ /*
+  * create_foreignjoin_path
+  *	  Creates a pathnode corresponding to a foreign join between two
+  *	  relations.
+  *
+  * 'joinrel' is the join relation.
+  * 'jointype' is the type of join required
+  * 'sjinfo' is extra info about the join for selectivity estimation
+  * 'outer_path' is the outer path
+  * 'inner_path' is the inner path
+  * 'restrict_clauses' are the RestrictInfo nodes to apply at the join
+  * 'pathkeys' are the path keys of the new join path
+  *
+  * Returns the resulting path node, or NULL to indicate that this path is
+  * unavailable.
+  */
+ ForeignJoinPath *
+ create_foreignjoin_path(PlannerInfo *root,
+ 						RelOptInfo *joinrel,
+ 						JoinType jointype,
+ 						SpecialJoinInfo *sjinfo,
+ 						Path *outer_path,
+ 						Path *inner_path,
+ 						List *restrict_clauses,
+ 						List *pathkeys)
+ {
+ 	ForeignJoinPath	   *pathnode;
+ 	ForeignServer	   *server;
+ 	ForeignDataWrapper *wrapper;
+ 	FdwRoutine		   *fdwroutine;
+ 	FdwPlan			   *fdwplan;
+ 
+ 	/* Both outer and inner of this join must come from same foreign server. */
+ 	Assert(IsA(outer_path, ForeignPath) || IsA(outer_path, ForeignJoinPath));
+ 	Assert(IsA(inner_path, ForeignPath) || IsA(inner_path, ForeignJoinPath));
+ 
+ 	/*
+ 	 * First we try to get FDW's callback info.  If the FDW has planner for
+ 	 * foreign join, let the FDW plan this join.
+ 	 */
+ 	server = GetForeignServer(joinrel->serverid);
+ 	wrapper = GetForeignDataWrapper(server->fdwid);
+ 	fdwroutine = GetFdwRoutine(wrapper->fdwhandler);
+ 	if (fdwroutine->PlanForeignJoin == NULL)
+ 		return NULL;
+ 
+ 	fdwplan = fdwroutine->PlanForeignJoin(joinrel->serverid,
+ 										  root,
+ 										  joinrel,
+ 										  jointype,
+ 										  sjinfo,
+ 										  outer_path,
+ 										  inner_path,
+ 										  restrict_clauses,
+ 										  pathkeys);
+ 	/* Returning NULL indicates that the FDW can't handle this join. */
+ 	if (fdwplan == NULL)
+ 		return NULL;
+ 	Assert(IsA(fdwplan, FdwPlan));
+ 
+ 	/* OK, this FDW can handle this join. */
+ 	pathnode = makeNode(ForeignJoinPath);
+ 	pathnode->jpath.path.pathtype = T_ForeignJoin;
+ 	pathnode->jpath.path.parent = joinrel;
+ 	pathnode->jpath.jointype = jointype;
+ 	pathnode->jpath.outerjoinpath = outer_path;
+ 	pathnode->jpath.innerjoinpath = inner_path;
+ 	pathnode->jpath.joinrestrictinfo = restrict_clauses;
+ 	pathnode->jpath.path.pathkeys = pathkeys;
+ 
+ 	/* Use costs estimated by FDW */
+ 	pathnode->jpath.path.startup_cost = fdwplan->startup_cost;
+ 	pathnode->jpath.path.total_cost = fdwplan->total_cost;
+ 
+ 	/* Store FDW-private information too. */
+ 	pathnode->fdwplan = fdwplan;
+ 
+ 	return pathnode;
+ }
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8a3a5d8..32d1bb5 100644
*** a/src/backend/optimizer/util/plancat.c
--- b/src/backend/optimizer/util/plancat.c
***************
*** 22,27 ****
--- 22,28 ----
  #include "access/sysattr.h"
  #include "access/transam.h"
  #include "catalog/catalog.h"
+ #include "foreign/foreign.h"
  #include "miscadmin.h"
  #include "nodes/makefuncs.h"
  #include "optimizer/clauses.h"
*************** get_relation_info(PlannerInfo *root, Oid
*** 347,352 ****
--- 348,362 ----
  
  	rel->indexlist = indexinfos;
  
+ 	/* Get server oid for further planning, if this is a foreign table. */
+ 	if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ 	{
+ 		ForeignTable   *table;
+ 
+ 		table = GetForeignTable(relationObjectId);
+ 		rel->serverid = table->serverid;
+ 	}
+ 
  	heap_close(relation, NoLock);
  
  	/*
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 1df727d..2228ec4 100644
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
*************** build_simple_rel(PlannerInfo *root, int 
*** 116,121 ****
--- 116,122 ----
  	rel->baserestrictcost.per_tuple = 0;
  	rel->joininfo = NIL;
  	rel->has_eclass_joins = false;
+ 	rel->serverid = InvalidOid;
  	rel->index_outer_relids = NULL;
  	rel->index_inner_paths = NIL;
  
*************** build_join_rel(PlannerInfo *root,
*** 369,374 ****
--- 370,376 ----
  	joinrel->baserestrictcost.per_tuple = 0;
  	joinrel->joininfo = NIL;
  	joinrel->has_eclass_joins = false;
+ 	joinrel->serverid = InvalidOid;
  	joinrel->index_outer_relids = NULL;
  	joinrel->index_inner_paths = NIL;
  
*************** build_join_rel(PlannerInfo *root,
*** 441,446 ****
--- 443,456 ----
  			lappend(root->join_rel_level[root->join_cur_level], joinrel);
  	}
  
+ 	/*
+ 	 * If both outer and inner are from one oreign server, maybe this join can
+ 	 * be pushed down, so remember the oid of the foreign server in this
+ 	 * relation.
+ 	 */
+ 	if (outer_rel->serverid == inner_rel->serverid)
+ 		joinrel->serverid = outer_rel->serverid;
+ 
  	return joinrel;
  }
  
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 072d50c..0fb6b60 100644
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
*************** set_plan_disabling_options(const char *a
*** 3168,3173 ****
--- 3168,3176 ----
  		case 'h':				/* hashjoin */
  			tmp = "enable_hashjoin";
  			break;
+ 		case 'f':				/* foreignjoin */
+ 			tmp = "enable_foreignjoin";
+ 			break;
  	}
  	if (tmp)
  	{
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a71729c..013870a 100644
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** static struct config_bool ConfigureNames
*** 756,761 ****
--- 756,770 ----
  		NULL, NULL, NULL
  	},
  	{
+ 		{"enable_foreignjoin", PGC_USERSET, QUERY_TUNING_METHOD,
+ 			gettext_noop("Enables the planner's use of foreign join plans."),
+ 			NULL
+ 		},
+ 		&enable_foreignjoin,
+ 		true,
+ 		NULL, NULL, NULL
+ 	},
+ 	{
  		{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
  			gettext_noop("Enables genetic query optimization."),
  			gettext_noop("This algorithm attempts to do planning without "
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index a18f14a..34362cd 100644
*** a/src/backend/utils/misc/postgresql.conf.sample
--- b/src/backend/utils/misc/postgresql.conf.sample
***************
*** 240,245 ****
--- 240,246 ----
  # - Planner Method Configuration -
  
  #enable_bitmapscan = on
+ #enable_foreignjoin = on
  #enable_hashagg = on
  #enable_hashjoin = on
  #enable_indexscan = on
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index bdd499b..e4d3426 100644
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
*************** extern void ExecFreeExprContext(PlanStat
*** 332,337 ****
--- 332,338 ----
  extern TupleDesc ExecGetScanType(ScanState *scanstate);
  extern void ExecAssignScanType(ScanState *scanstate, TupleDesc tupDesc);
  extern void ExecAssignScanTypeFromOuterPlan(ScanState *scanstate);
+ extern void ExecAssignScanTypeFromTL(ScanState *scanstate);
  
  extern bool ExecRelationIsTargetRelation(EState *estate, Index scanrelid);
  
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 3378ba9..e535e12 100644
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
*************** typedef void (*ReScanForeignScan_functio
*** 68,73 ****
--- 68,83 ----
  
  typedef void (*EndForeignScan_function) (ForeignScanState *node);
  
+ typedef FdwPlan *(*PlanForeignJoin_function) (Oid serverid,
+ 											  PlannerInfo *root,
+ 											  RelOptInfo *joinrel,
+ 											  JoinType jointype,
+ 											  SpecialJoinInfo *sjinfo,
+ 											  Path *outer_path,
+ 											  Path *inner_path,
+ 											  List *restrict_clauses,
+ 											  List *pathkeys);
+ 
  
  /*
   * FdwRoutine is the struct returned by a foreign-data wrapper's handler
*************** typedef struct FdwRoutine
*** 88,93 ****
--- 98,106 ----
  	IterateForeignScan_function IterateForeignScan;
  	ReScanForeignScan_function ReScanForeignScan;
  	EndForeignScan_function EndForeignScan;
+ 
+ 	/* functions below are optional */
+ 	PlanForeignJoin_function PlanForeignJoin;
  } FdwRoutine;
  
  
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index ecf62b3..2110ee4 100644
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 66,71 ****
--- 66,72 ----
  	T_NestLoop,
  	T_MergeJoin,
  	T_HashJoin,
+ 	T_ForeignJoin,
  	T_Material,
  	T_Sort,
  	T_Group,
*************** typedef enum NodeTag
*** 219,224 ****
--- 220,226 ----
  	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 535eca7..853f827 100644
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
*************** typedef struct WorkTableScan
*** 440,445 ****
--- 440,446 ----
  typedef struct ForeignScan
  {
  	Scan		scan;
+ 	Oid			serverid;		/* OID of foreign server */
  	bool		fsSystemCol;	/* true if any "system column" is needed */
  	/* use struct pointer to avoid including fdwapi.h here */
  	struct FdwPlan *fdwplan;
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index ecbbc1c..d3309ac 100644
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct RelOptInfo
*** 414,419 ****
--- 414,420 ----
  	List	   *joininfo;		/* RestrictInfo structures for join clauses
  								 * involving this rel */
  	bool		has_eclass_joins;		/* T means joininfo is incomplete */
+ 	Oid			serverid;		/* foriegn server, if foreign scan/join */
  
  	/* cached info about inner indexscan paths for relation: */
  	Relids		index_outer_relids;		/* other relids in indexable join
*************** typedef struct HashPath
*** 939,944 ****
--- 940,955 ----
  } HashPath;
  
  /*
+  * A foreignjoin path has no additional field.
+  */
+ typedef struct ForeignJoinPath
+ {
+ 	JoinPath	jpath;
+ 	/* use struct pointer to avoid including fdwapi.h here */
+ 	struct FdwPlan *fdwplan;	/* FDW-specific information */
+ } ForeignJoinPath;
+ 
+ /*
   * Restriction clause info.
   *
   * We create one of these for each AND sub-clause of a restriction condition
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 604df33..58cc64a 100644
*** a/src/include/optimizer/cost.h
--- b/src/include/optimizer/cost.h
*************** extern bool enable_nestloop;
*** 60,65 ****
--- 60,66 ----
  extern bool enable_material;
  extern bool enable_mergejoin;
  extern bool enable_hashjoin;
+ extern bool enable_foreignjoin;
  extern int	constraint_exclusion;
  
  extern double clamp_row_est(double nrows);
*************** extern void cost_mergejoin(MergePath *pa
*** 112,117 ****
--- 113,122 ----
  			   SpecialJoinInfo *sjinfo);
  extern void cost_hashjoin(HashPath *path, PlannerInfo *root,
  			  SpecialJoinInfo *sjinfo);
+ /*
+  * cost_foreignjoin() is not defined here because the costs of a foreign join
+  * is estimated by each FDW via PlanForeignJoin.
+  */
  extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan);
  extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root);
  extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root);
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index ee02732..f824f91 100644
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern HashPath *create_hashjoin_path(Pl
*** 93,98 ****
--- 93,107 ----
  					 List *restrict_clauses,
  					 List *hashclauses);
  
+ extern ForeignJoinPath *create_foreignjoin_path(PlannerInfo *root,
+ 							RelOptInfo *joinrel,
+ 							JoinType jointype,
+ 							SpecialJoinInfo *sjinfo,
+ 							Path *outer_path,
+ 							Path *inner_path,
+ 							List *restrict_clauses,
+ 							List *pathkeys);
+ 
  /*
   * prototypes for relnode.c
   */
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 51d561b..9b8eccb 100644
*** a/src/test/regress/expected/rangefuncs.out
--- b/src/test/regress/expected/rangefuncs.out
***************
*** 1,17 ****
  SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
!        name        | setting 
! -------------------+---------
!  enable_bitmapscan | on
!  enable_hashagg    | on
!  enable_hashjoin   | on
!  enable_indexscan  | on
!  enable_material   | on
!  enable_mergejoin  | on
!  enable_nestloop   | on
!  enable_seqscan    | on
!  enable_sort       | on
!  enable_tidscan    | on
! (10 rows)
  
  CREATE TABLE foo2(fooid int, f2 int);
  INSERT INTO foo2 VALUES(1, 11);
--- 1,18 ----
  SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
!         name        | setting 
! --------------------+---------
!  enable_bitmapscan  | on
!  enable_foreignjoin | on
!  enable_hashagg     | on
!  enable_hashjoin    | on
!  enable_indexscan   | on
!  enable_material    | on
!  enable_mergejoin   | on
!  enable_nestloop    | on
!  enable_seqscan     | on
!  enable_sort        | on
!  enable_tidscan     | on
! (11 rows)
  
  CREATE TABLE foo2(fooid int, f2 int);
  INSERT INTO foo2 VALUES(1, 11);