WIP: Join push-down for foreign tables

Started by Shigeru Hanadaover 14 years ago24 messages
#1Shigeru Hanada
shigeru.hanada@gmail.com
2 attachment(s)

Hi all,

I'd like to propose $SUBJECT for further foreign query optimization.
I've not finished development, but I'd appreciate it if I got someone's
review on my WIP code and its design.

Changes I made
==============

(1) Add foreign server OID to RelOptInfo
I think it would be nice to know whether a join comes from one foreign
server or not without digging into child nodes during considering paths
for a query. So I added serverid field to RelOptInfo, which defaults to
InvalidOid ,and is set to OID of the server if the node and all of its
children are from same foreign server. This also avoids looking catalog
up for foreign table entry to determine FDW routine.

(2) Add new planner node, ForeignJoinPath
ForeignJoinPath derives JoinPath, like other Join nodes, and holds
FdwPlan like ForeignPath node.

This node is used to represent a pushed-down join between foreign tables
and/or another foreign join in early planning phase, for all of
combination such as table-table, table-join, join-table and join-join
will be considered. In addition, though they might generate same
internal (FDW-specific) plan, reversed combination is considered;
planner generates two ForeignJoinPath for both (A & B) and (B & A).

During creation of this node, planner calls new FDW handler function
PlanForeignJoin to get a FdwPlan which includes costs and internal plan
of a foreign join. If a FDW can't (or doesn't want to) handle this
join, just return NULL is OK, and then planner gives such optimization
up and considers other usual join methods such as nested loop and hash join.

A subtree which has a ForeignJoin on its top is translated into a
ForeignScan node during constructing a plan tree. This behavior is
different from other join path nodes such as NestPath and MergePath,
because they have child plan nodes correspond to path nodes.

(3) Add EXPALIN support for foreign join (currently just for debug)
ForeignScan might not be a simple foreign table scan, so
ExplainScanTarget() can't be used for it. An idea I have is adding
ExplainForeignScanTarget() to handle ForeignScan separately from other
scan nodes.

(4) Add new GUC parameter, enable_foreignjoin
If this was off, planner never generates ForeignJoinPath. In such case,
foreign tables will be joined with one of NestLoop, MergeJoin and HashJoin.

Known issue
===========

I'm sorry but attached patch, join_pushdown_v1.patch, is WIP, so
currently some kind of query fails. Known failure patterns are:

*) SELECT * FROM A JOIN B (...) doesn't work. Specifying columns in
SELECT clause explicitly like "SELECT A.col1, A.col2, ..." seems to work.
*) ORDER BY causes error if no column is specified in SELECT clause from
sort key's table.

Probably more problems still are there...

PG-wrapper as sample implementation
===================================

pgsql_fdw-0.1.0.tar.gz is an WIP implementation of PG-wrapper, which can
(hopefully) handle both simple foreign table scan and multiple foreign
joins. You can build it with placing in contrib/, or using pgxs. Note
that it has some issues such as memory leak of PGresult. I'm planning
to propose this wrapper as a contrib module, but it would be after
clearing such issues.

Regards,
--
Shigeru Hanada

Attachments:

join_pushdown_v1.patchtext/plain; name=join_pushdown_v1.patchDownload
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);
pgsql_fdw-0.1.0.tar.gzapplication/gzip; name=pgsql_fdw-0.1.0.tar.gzDownload
��opNpgsql_fdw-0.1.0.tar�\{wG��������d���DZe����D�$>I��.�-�nw7R4�|���[U�9�����I���U���U������<���L����x�g�����S���_�����8:������������g'v�-� H>6��������	'X,��������������s8�������;�G�{xtx��;qwl{�g��8��<�N��������i�6w��=�^�8|����d���;_�����p�/��9�o��|�=��?x\��Ge���<
�?���&.�/#;����c�����'ck;��r����f3����O��o�������o�������bo��w������y&R
HeoY���7W������l��}��A%7�D&^���+C;�%>9������h9�����p!��Q��{uuv1���_kso����S��W�������x�_�-����}3�to
���
��[[P�&�XV�}�o���7q�D��G����K��U��g�����Z�J���L���������p�s\�<��/]�/��-9���|'�Ry��l�$G��7w��fs������o����0+����6��[jrsqkI��&������o��_��V�>n��/�^����/�����9}��J��$���'ZA�y�Y"jN]����6�M���Y�W�����8�9�o���M[���^�6��[O�������'�E0�!������Lf�������\;��}d����j�s"#���^'�H^_5�(@���(�(��\�6�}wN@�>��l�����y��rl_��X�H��@��#�x"�Xv��"���/0�f�����{����d,D�<�V�)%���CE�;o_��E0~�h#�L<�Yf����a��9����?����	�/�%<_$ljs���QL�@�9Y�epO�up��g��xQ���`)|���1c������)L�<��r�Yz��q���F�M����,��H
"�"����>5s�<l��0��}'a���r�yW�P���Kf"Nd�������*�u�V����A��3���"YG&a����GK��������j�F�j��U���F��@,KS��F��4�X�evZ��!�lh�
XT��8X��8`���]��������r-'�I�F{�)+y��
n��h���"���kh�Ifv�,�%��C��;d2S��`�C[����S�Q>��=G}���d_`�����k�A��"��*�O����.8�*����?�����s���a[��`lU�76����{x4���K���H��a|�U����k?~t~����a���+���:�lUj�"
����+"v}�3���=���(�?%2�7`�9�,�A4��UP3�X(�g�����G�/��b����n�������l���Y@M!l=��Q�aLl(�6�HA����{���}���`��i��{V��������BI8�(��1�%��#�37`;�Ta���A��`fB�Xg������j��0NzU��Y�pS��I���H.`�O.G�RC��d��V�DY@�[����aT��x�9M�9R��Z�|���8���L��\(]<29���4���?
��L�����f� �^�����m:F�;������HN�9$VW�|�%�"�����(����p����.�8�lh�������*z���Y�����iLI)j��}�AI;KA��\lZ�\c?���Wma��ln��G��Qg2���pJ#QXxS/����.P�pkDA^c�M����]Ra���I�����mBr%wr�.I�&@��KL����&�J��Kx��1rnC�G��t��A��[fkf&�P�����R"���6��T�����M��������e����_bc����.����9x��-�
(u�6]O����)*�x��A#�z���/G)���t���-���~�=�?�Hx0�0�K�42K�`k�V�G,�&1�l��:���ACT�j*�*$J?E��b��U-?`a���r
�y��DUU-������dlS��(��T� 	6����|�����M�
�nJ^��zE
����������o6������;��������N����[�p����o��$a��T�5�����N��>^=�axMjv���J��$\�Ae������1�3�D�������~,y��)�Q�|�L&d��0d��l���G�Sk�n���6H��~�wZ���S�L���F���E��
ND{9Kv)1�C�7�Q^��/	g����#H�L�X�	x���U��[�g�v�TS`������=
���LqT�I8'I^�>X���H�s���0����M����
��}�z��]rG��I/�T?�Is�A_���q�P�\�)�I��#�Ji��y���5ujY$������C���E6b�7�|4�C�^B����y���^����X	�4c�;�.?L��_@�^�jw�������A�L!0#X����k�?2�n�C��g�(��a��;������`���j���EnA�o�����3���),
;�*�[o�9�0R(�7��T�
�*�RN���P)&Q�0��*.��J���4 K�R~}�F�m�h�[*�S�)c��2���d��e��QrY���Ml���S�d<o������^����B��*���,&�������Vu�-%�]�C7��u�i�Y�Xl�Q]1�^�r�����N��X�4����F�2�<`GZj���{���<���U��r�\w�z��-�yS������>g��mii���oR�4��*��s����T@��T�
AU���v�H����oV�!2Q#�"���2jy����m4��;5a�Qo��T��
b4�U�*�?6f����Z��3�oY�ans�EPB������;9?G�@#t?|��������FR�|s�j��'v]��U��D�G�@(sG$��d���������j���1�<���@�Zh�E�f�;���2���
<U[�!5��j�U�r�����AA����yx�9_G)��S����[�:u�T��2�I1���Q��������I�&%)����dL�m"g����,p�G|�C���n�8��vg
��hR��;�E�:&�V�d��Zi��-��gR��/c��	;Z��sj���E�9R���eY�!��ios�I��N��b���
U��{�b��S�f�Q���FG/�O��s���d�h�s�>R5p
��2�D��L�r-?�T9ZP[je�������r.Zu�8`?�A�&s�)m�%'^<SI5�l,�����"���l�2���zH8"W����r�4��8�xf�����	,}�x��%���<��E���XF1+�������e�����X����d-�T+C���v��o�0L�F6�A�h��F`��W��������,�����j���U����@��>8�7y��?r�w������?��w�^������y��wx�7?�Y�x�Cl����/�+!�z��
�������:��;����Nh�DA��.(��`:���a�U��|��"��VB�=%��g��V����Y��
k�������%�[Y]�����A�)���"�(����Y,�H~�+Tm�b��w�8�3��Y���&�N���)�����|L��&�x�-!@�P����
���(�����g&^����P�MCu
� Z��MK���*`��E�_��PN���`u��[�T(������M��W���Nh].��	'*�@u�$����v$'�	}�����y!���� �db�>���|!�+V�R"��0�2eD��07���B�r+	5��G7i$��|/���jJBIs���E�
s�g����A%��s+��B�S��b�rx�Rl�d�%���'m�a$i_��;>�������Qxx��P�n�2���R�N�K>k������
�A�� ����z�K��� �"����Fb�������)�O����R"��C%�M5�@�.�k�Z�����B]���
��+f$s"gV%��J�u���JY=�rM#���D%�N�)������N��(�����������/�
��R?�te���l!���d�>�-�
���o�_vV��AM`�.]n(�����|W��Q�A� ��<���Q�xT��+�<Q+3��[P�<\��W����i��;��M�#)���r-�������T������m��8���%S�_��E�0`E���u�"]]�N�O�;�`����`���m�)w�;L�H>���h.������k�kJ������u�:�Z|P�[��a��!��d��I>nb�Us��	�����oE��o�L>���)V$�Z��?T��~C|��G��z�/J�;�����/�����~G�������y����������R]������~!|X����2�EnC�E� �y<��`��Ri������M�;r>�\T��;�P����?�9X���+B�If�Y3��� ���y�@>�)Y�2$'(�(��cD�;�t�"��a����5��s�
\B���S/��R0i�\(��V���E��!rI�x/�k�)[J��i�{�a���P�
?�KN�<��K��7��ZNthe�0P�4�6�?@V#�2k����f���[9Yu2����Tn�W��u���S^29�<�2"h^�3{�?&��U�(Z�FIE�/cQ���W�T�4%Vb1���e����	�g5�W?��~{4��w��e��7�F��1fE�/&��
?�����t@�������	r���n�G�
�s��
i��e/U;{,I�BY�KV���pJ���k�z�A�TC1=���<���|Og������}ul�nmz�`c�Hg!�U��L:�l�����E\��������0�nNq���O�>\
A4gG.�������H�Nn\�I�ni�B�����G"�������z�cE���L�8����8�:����	U6�u�Bv�W_��ax�����qg�������������}���*��V7������`��G�~����3l���5F����}
K�
�����ZOiOk)R73��I�6S�zS�*��E�z��x���"�u��@M)3�V��o�����4��=xOJ077KFF���*1��{|U�B��~��k�w'Ydj�B.�sL�j�_�AR�"�(DG-�@>E����>�?y�`����Sa
OE�6��@���9u��8�W �.&:������g���d���J�����E��~�=vtK2�4+E���#]��t�2��'tXMQ��?Xp��n���m���avW3w���H��#UW�'�	��$������f�M��.�^��^�C�y���C���k����:����)����Ww��v�uh���S�CN���*�����B��\�|RiHn6���?t~��O�&�����40_'
����1"~g`�'b���]iK������\���}U�c����y����T�?`G�����(���a���o����78�������
B%x�h\��\�w[O����\�,�G����H�Z�����N:4���t�"���6k���i9��r��I�{��Rw�[?�!vL��^��VQ]�*�����M�E�����?�d��j����u�����
��
����.�w����j]��L������B�cFS��X�2��ZM���Z�Kpy�{�E�5�H{*-�#hQ�
� 9de+$�\�p[.ML
5]����*�D��X���]|��������������8��@��|�	�Tc%�"�B@��
0������Bw(����UM��<e'�2�8;�x���x��N&��3q��������+�g�kUu7@P^"���[w9��s�~N���REVG��q�y}W���k��1	d���2�
6�D]c�����0�����.��2�b������
"��\d'���ah�������$�r���r�E
��D�a�W|h�7t@����ZR���1pT��3w��U�/B�q�Q���zGQ[�U[E���7��� �"��B���'�O��"���I�l@7(`��Q��R�+���3��S&U|��9���y���MtuX1T�<�����u@S�3#$�[�s��;�����a�pD�i�(F>�r��n��1�6E�0h�l���D��y��]�n'^��]��i�	�}��A�8"�20�;���\4�����pO���nc��2��w�7��)0U���R��Y�%�V������6�pB�����������J�s�o�<D��X����?������T�R�n���c04&&��H�1@GLr	K�C�����7�7I��6""��S��y��j��38h-��F$>9�;QP�(���F�P�%�"��Z-Q�^e�I��(��*�	
�� ����
�/� ��q��+���������I�#�/���=���
Do����N*�y��f������c��-X���RY��e�k-�=���5�]:�F��!8�%�b����e2��5J�EOs������U�k��Ei�&�c�Mi4"���Q�Jw�����Z��J�����O��B#�P�X<��+h���z��W������k�������'����bt {GH�ls$hb	���|�e�BQO�$F�%G�= �!8Y����KL������rmt^�-D6
�'����C�-<�]%a���{5��Z��#oN"�|��%�hJOB���������j�cNZ�'s��fT���c��T�}�@h����#���(?#�?�/j�1�����s�?�_\�(�����D�?���?�����a}--����I���<>�y�D+4��4:���*I[�in��Q������p�n���g����4���������/_y.��������P����@��tIc��]v����!�J�-{���������^,�t]�>��/�)�K�w�3%]��^�Zi����-�0��J�0X��	�������f+*
(�vY�+6���)+cFD�K��-�N��������yMv���Q��/�n��0j���73T6�r��&�=���T��N?�O��A�A���U�s!j��i�T��x�Z`	.���Qv��~b���C��&B!T�1���Er���m��L�?HZM����Z�Uv���q�|D�����'�P�d�����"����R��A�<��qp��56a��%����@�}r�|cv8��
}�����SQE���w{�*q!���������I�#Nc������ �Z���m�W�"����g���%����*;���z��7�2�*O��aB��G{�)��_����:���k��B�������&dS)Bp���N��M�E��K�=S��M����w#:�w�.}�����>`/^����]���.�3��"����������p�����^�����lTQ��I�=���M�L�����~��#��<(3U���L������
hA;���h���zj~����k'��5���9>�0a��t�TZzD��|���|K0��\�H�8SsR`+���>0�
���
�@}e'�t��r(���i_���~�NNz��vq�	��*�����cBJ$�>N��a����	�dt���*�YL)�Ni�\�rt)3��U.���s� �d-����`*�_�	�Q:k�w���t�)�L�D���+�n��� ��^���H?x�]*L�d��b{R&����\�\��6�,�|�C��{����^��Mu�@�@D&{d��q?h8��J��b���9�*�		i��������*�������;	�I��M���D�rh�f�&J���=d?#�����`G��h�T�R��Ial�������3����*�;�}#����m'8�������!�Dm	�^.���B���\;d����r�)��Ut!��A$��l��-�����/IF1���_��^����-������>w��
me���x���s8�_R��M|��C)9��O��"��	pKN�l�y*(i0�@�?���Nue��S��+x�Kl���,��7J�j���=�Ok�x&'V��z��{���R7���0����4��-|���a����c����t�h8�,I(4$
���}4b����j��=l�R�	�('�� <��;/U����F��~}�m;S)��Z���r){��*I���@v��&�8�����1l������<�)8�gV����K7=%13����^r�P�<;�
MXc���������) �����x4�}&��G_�O��`�0R�U��L>7������
���7�vn�b2Q��Hlc5kF�:e��8��Q?�������1��.w4���\9P���`�]���&�p������=4,���W������4��Z[������3/ms��=���X?���R����K6tTv�����
�H����ai���������t����������2�2���P�1��\'�Kd����B��}(�Po��s8���C�1���:�����
L����yhq�p?��]�VN��fA�����U�d��:�Z�v�f�R�_/B�[�i����4�*Y2��O�"��:a�G������ei��J�x�Q��9-s�o�zO���L
3}0��"M�S?{��5:�c�Z�S��E���^/u)u�f�����[AR����$VR�M�v�������k~�&F'����l���(����*n7 �R7��?�>_JP'` ���w��\�+�h�L���T��Fy��89��+Wv�������l����p"����#�������]�%(��>ioD�\j����7��st�
tl���\���IzE�-BL8Z��aan 0��X��r�^��VD��u=�s�MRx�Hu*Y����s��0�%~���!
����!c6��������&	��J�����9�f�V�y�5��X�������;#����)p���=:����G����a���*�[C-�0���I�qu')&�@�vx@����x�j:x1w�:dg�~�w�r+U_+�������J����:T������ax��Z�5���5�,'�P�z�0���s�:Q��2s�)�t����Ut\m��%8��#��	Q�&�!Q�p
4�4�FI�*Q����t<�����^u�
�C�N*���.� 1��{�A��t�{�&�a(6~�)�G��\��1#��l�,6��+��(Z����<v	���zjLM�&D������E�����4�C7�7��B,�'�iX@���&)�D3p���T�.���F �BZ��:�������v�]X���f���[�&��+fD�@!�!����8Me��X�V��oP�������u������-Z`_1:��=:�g��.��M�gS�o2����x������-+=�g��e`
\V*5_Y@�Y��!���X4W��/D�|�����f���g!�����^���#��6M�X�v"w~ �Yd
<�e��2�R�-��n4)�{�����(m�&��m�����/7bgie^�}�pO����;����*k#E:�Q�>���1���7����)1,N�q5Kf��9�DA���a2L1�5�,��q/���`+��.���Jwa;��E��������5����
u�M7w�z�:����v��������p�z�9��\�vaG�H`�F"�!�qw"�:��?��M����Z�E�Me�O"����h,X�h&����@��
�,C2�BP�?I��O'\���lD�t����?��?��/����T�3%NzI��[H����$�"*l��c�xT�JV
f�+���\%�K����vM#1V�>���x�)�u/u���l��6�����B��>~�8���t�+f�*,VbD�XQ�������4!C���A-�z&B�0/+����1���N��E�"u=����4�[-�G�qc�:���P^�EM�Dn�]8}r��8�pnR��dN����Q{8�J���T���kW����5:���lO�r(7*0t�^��@�2�W�������E�����eAP`���X��Hl�����!>��]���LTa�����dTc����X PyO��1��y�nWJ3#���}��1K������-�{��m�{�SB�!y���\����s�z%���Q�t�R�|�f�������:^B%/��^;��NuKu�UWA'I��S�ap�VY���
�!w--)�LHe"x����)O�]mJ^���PX9?:� =%4iz�L�2pk��?�J�������b(vC����Xi��X~D�8B�kC'�;�u�����.���ff���P�L���t���"0�!F�E7m">Tl���f����?����EY�*���T��&�|p��y��\��n��4-�D�R��������-7�_Y���^#U�����$�.L#O��������ewR��X��,k�Un�{���EiD����e����G�����*e2�;L�M0vOXW>C�EfJ�F5R
�1�2�����l��n���<����W<�������3wj�V�@l��k���u	&t�:#��`q�NyK4��}7YSX��]�o^���i�����-f����"<���vjG��b�,�w!��pS)�y���v�I�m�|��3vl�b
5���Ux�����(������\|y;�	A
;F��\�M+B$���%���W�3Q�������b��iJ����X�`4������.SQ5���9���<���$h�FP4�����[\(-�T|1Qr�\�m�6&�m����r�cr*H�)$����n�6�S����h��&����!_x����������������EO���?x�g��[�wY���c���������KW>���a������z�����f��V�{�����`fna��������F�H\�_\
(�N�rN�YX�o�3�Y�_YX899�?�<��{����^��Y{���������*xmzz��,�:�5�h//�������n���{�O$���������-��@+f�_���(^�9��3�w�N�z��U5_x_J�� ���"�W��F�B�^;z��,`n��=H��v��8����]���c���j j1���n��=�Y��]��8V`�P!8�wO/�Np�U��K�����������I����m{}����Eh����=��|u����HJ�a@O���9��|=�4��������l��=b&r�hNwl?�x`A�����8��+��F]������<|��������w���������A�g@�������}��~��w�>�j�af���������w�����?|�=�7�	 �|$��g�=�����_���7���w�5���P�����@���JyK.'3���^J�x��������4������/��F~��l-n�`X�P��=s����������g���%�,Xy���7�.��h��`��(��8�����<�������w�}����������f�T�z^��7�����Ss�#
h��o�������;�����Gu��$����������������; ^�����������w~����Nw#{�~��W��E8`�|��
����g_����?|����������b��B�|������w����G���0I'��������p����/���~�_���|����7��-���6d#V���w��? ���_}������!�x��0�~���?��#��*�t����O���?�����F�f�A������_}����I�8���L����/C�������� �;?161�$��������o=|��������`}��w>x�����@	`B'�?�6`�/<xy�M�q!�M��7����h??���<�����U<��o��N��o�������w���L�|�������e:���������_��g��M��r����@���;�>������?�9{ }��������}�����2���,��K�K���E�������>|����7>����{@���������p�>��|��+�:g�����*�����������F}a���W���A��9���8��G/�/>���0#/�<^�$�.����O	?�G���_����g��%d���1�9�2 ������m�i���J`�Sb\����0M�����,/��q��������5����_x���DJ����
��_��&�E�r8�����e���,�3�Hd�^n����
�"���6��������?�g_����_���o��?/�p=�<Bq����Jc�����V����!y�)���m����/Q�9��3��4}���f
g���	��K�`�W���0a���"��F����+��+�&R������?>��/��|�����
��t�������;_}����P\�w�/��;���W_�C���������G���������:|3�z���j��$�~/h�����F��	O��$�/����<�����y���[�#�c^�}^zv�����@N}�F�)�0�_��`�Gn�������x��}���n�
�9|�������<�K�����&
jzW��I:����X���sq�����OsW����O���7H�|�n��^��w�������E�9�=��U��/?X�����������6Q��>k�]*L�����5X	������
�.��~�?���b��j��+^�'���������7|8���]��#{����t;���W�����3�g����w��kPT����|���h1_%��Ods��o�4"�N7��������?��<����p�V'�����V��w�w�mG'��z?*%���(_������������~����m��g�r��d0m�U{�����P�a�o�bzo���c������@e)$'�i���l�(y���}��ggb���(I|�W���������F!�8X�"��_t��ExKx�|$@�_�W�X~��� ���o�_���G?�6�������t���_1�b��/�#�A`����(�(9_�6_���P":P�_��_�������X('��!��8�����C=����_���>����W_��O����75��D-�����@]G�[%����l�w���7�,x��[������Q=�},���D��A��,����Z�����sW./������!f��b��G���3knA���n��7�o�Fa��G�����D���;f�W�����������-�}F�{$�1Z~W���	X?^��xd�q�PO�
Q�����7�B���ld��C�_�����'%���B�\�L������O���{u��`a�E��!���������,@�|������ ��$������]�5�X�nE����H�8�&�9mR����D��E����_@��V?�-a��������;��<b@�L�7��O�Hs�'�z�������x/�[86)������.(/�u��t�Z�Ev��e�_��p�uo*;�/CB�O~�o_�G�t�Z���-��f������hu�iK?E��D��A
u��AEQ�X���-:�5��A��� ������aL����Wy�J�j�+=��L����|�g���#O<�Y��Yn���������������'���)B�
�K$Fdy��=��7���<��{�Q�D|�����)>Dh.���.����;D_����t�s���
C�P^Z����&z[9m�1��"L,�d���� X#���H��d�'4FQ����������TZ��E[[2Vq}com������� B�a��g�����!�~���o�R�����~������ie���_��>B���7��|��~U~@��m���V���MUs����\T9�p�0�,�'�L����5(Hb��w����A�����"�Z����5�'���[%Rb�H����iYLJ��N%?�(Feb���1��b,�*8G��~�m�?�E�����!��K;�{_��G_���y�>��_������6����D6�M��������|��t�������2[/������O������}�Uk���3�������}"m��)l��|~\,�^P=���JW���

�Hc��G��3~KZ��O��<�������u���^T�k��}t��h������1h�+���Y<e�8���������1[?���E���/��H�.)�5jW-����b]P����I]�\JZ����G��'�r1���/j[��N�L"lj�ne/g�<��������k��������2��U�0�� ��1��cS��s�O������e�t~�@�T�U�.�{������,]�Q3�����m���-nE�����������������xh�������CN�=��ODelh����������x�������(f�3������}����K���;p���?��?��?�����W��,�!A��P,9k6C��m{P_@?�d����=�R	?@P�6n KUx������c�7w�������"g���Fs��gB���Q�{�����w~����7K����G��:�����+Q�?����i��������VXF5u��S;Y����u���i�pEK�����3�����R��7��y���;���o	�����sS�&�b��!�)J�_�����m����O��f9��)�c��w��^�n2D�$}N4-��������&����!�V�����E$����&�G���pZ<	���fB���:�$����N�k����|�T�~|f����&jo���g�L� ���C�[h5*�����������-�n-���C����x����{�Q��������zc�&�'�G^;�G���"S2�3�#����Eb���O�Q��4.���D��
!�(p[R��.,�X9����GD��J<�{hnd���>���FO���XLk���|s`P���f���o�������IV�c�Rmq��O8pe��&�F�����Pd~��/<�?�G �R���p�yKe�7��
�����2��^��'�J�8g��E�=����������RKE��!���}y�����j���fu�W�-����
�F�h� ���R%:tg6��k��!���'������������3Y|KK�J@����5���9�E�
��-Y����?F�u�5�xD��quJ*]`9�u��(���5��������k�r*UY���{���-��)������G�.��$������"��+_���O)H�Jz�?]_�__���6�������*.5R��$�?,5*���/��\m?A��d0��;�q�� �y���������z}%���0m���2K��%�]BY~���zcms����
0��\�>�D�T�,^��n)>��2a��A�kG�6�"4�M��8N�dp:��o��yY��'�)8M��\1������6c9���|�hr�F�q�;�.���
��]F�ve����/�p��VatA���q7�h"E��
T^][�}���_\�^����K�'��|:QNp��G�����!�8�������]��$`�5��8����?H���Q��qjvLQ�R��i��w?��IA�����n��6<o	���$m\\��b���O+�����&��/���A+�4����1��_�{$\������E&��?��,��/������F�B����o��k����[��F}�����x�w����,Fh1�+�;����D1�8�Kh`�~��_������������<pB?T,Wa���$�R������D�?�����I,��/�F�z��=����,}�������;uemu���5�^+i�Ee|f\�GAox�AE�'��-�/���"�?y�����E���(C|��dD8���/�����1�v���������C�
������$5����|�]��c��'0A���SD=M)`�?P��wa.^<�\����w��/��Ex7�=:'K:!4+�Z�Nn�Q��]�#�H������Z.�~�G	���dQIQv�s�����O�����@4����B1����u��M��-������*��%����C|��D�������?�[���)��
��&u���kJ?u[����oK`�$�$��H�l�167��y�f����[�~�kB�/�v��n�nw���1]{�_�R���?��������Hh������~�1��v�b_����$���!l7�g3/�����`�q���-�m�[���S�Pr��Z�a�� ��O�v�����KK�`{sw��F}�~��&e~��vo�l�������[C?)�,�������)�S���Ho&���R���+3XLC��<V�����,i%�*����i�����2`F:��� �������*3O_^�i�������Y3�g���$9�Dsa/��fq+�o%���p��t��1��c���u�s���?f0Y��������f��i��(��g���{�lp���E��A��m��7��LUfn��--._��x����X�����'����������1f��o�7kc��$E�%U��t��9������E�cjt������/����_Z~���_���G�L3���a��������t3��;� ���,�X���!&~�J��GH�V�./>����vZt��Q���D��������p�����0�w'�t�1O4��Iz�9~qrt^��,?mE�'�I/����+/-���n�9]���
����K�G�{x�����hR��p�R{��}ly��v������c�s�Sa�v�JDWzI/��H�&��c��������i��L������K���1n}��A�h��@}����[���)��i�$�_�W���������K����" ���#���.����4p����0w�dY�]	���s�`r3)i�%���_*oC��M��0�C���m&�u��k|�<&|��^�������'�s�7����`��\�S.B=K"V�eFXzr��� �����a�EN�lA'XZ�����Y��P����������"&/Y����'��z��-�X!�����^���^*jA}����CQz_�+��wc�#�]���h�O�I�\G��x�E�Xy�=2+�:����; 9�tcv�*;����;Q�<������[#�e+q���BM�A�;R��m���\d�y����k���#s�<�/�q6�����X�iM�g}���u�S}Hv����:(�`t8�u1�q�':��V��v����9{�%���'0L4N�~�q�c������	;�XVd���TPc���v�vH���&�x��4�.4J���F����V�
;V���:�	�����E�r���?	�Ax�����e~����|/����!�1k������y���B���p����
���������3��*������I�����,&����C�����!}��S��|"�|�
���N�x`]L�q�(2�[Vq��Z?>���1b����|��_3W����cu��"M	�Z�a���o��l���	�������U�W�=/{=_X����/��+n����/������/���n���{Q������yz������Z!y^U�\���yt�\�sV�7?�������\*�rmQ8��/���|����_zi����f��Kc��R�������s�_��"jd���]p�|�������1_�P6��/�����1�<oA�����veq��K���Re�.�����A�
B�l��%��5���'��x>���{����!#�w���QH�,�|�7��|z%'�x���w�Ah��3{w��z��/
�{��@��Q����M_����C`UV����z�m��ZW |�5�U��N�+���E���nq1N�%���x
=R���W�V�Y�����������b����i�M�T����/����U�=��K����q�~^8G?Tl���$a��k���W�� ���`B~�9[��X'9J�H"W�B����:��^K��yt���~_�j��B��'��v�����9����^;\r&���z7�@yyB	l}}����bf���pt���rh8f�,i���=:��,6
��kc��+7��w�b�|�X�`�I�b�p�h�c��uE��0�2����h�|�#�Dz9�eR��D�����}���m�
����{h"=<�,lBZ�+�������xs:�������{����@_~~��/g_�{hBk��M�h�����g�j��M�+�=��AT���F��>�����5�u�����
_�k<�������3l;w��s 4��f'<�:��,�e��ag�6���
W'�O�����}�S�GYUs��%���)^M�KzP�K!�� �Nr(�J��=$�l�y}I �
>^�6�5�@�b:{�tv4���?CwV$H%�a�=����3�/_5QO
�������x~2HSq��������k2nYv
c��X?~������
D4|��|��x}�U�����4���a�?]���`����`Z�;|	�����17C�.t�d�J�~�^�k��K;�F���e��i%?�e�?���5J���?T�w_����)���+x����@�j�[�,��*�j�=g����LL�y�#|����e�w�������GXf�"������s��������S���h2���?��.����������>���~������3
���xp}��
���'�a!9#;I��;jiL2�����%�LJ�_*���r$��K�ed������rq�Ahrq��3w�\�88���
�KXV&�wT"�O�sO.����]	0��n?�<>x�<��N>���1�����5�#����1"�#�������`f?X��d��������!��5��6�A�����N����[k{�>Y�tPi-U��VRgfN����QP��g��3����3N*Y�r�Je]����������<3�J�����tc���:vlm��4��`r;���)�,�a��8����E� K��@O�^�E7�l���]z�����%���������A�"�-V���f�vx��VWV0[����n&�dz��`E�h��
�]QZY��7��Q��Y.�r1`f���fqy��VGN�g��I�=�(YEA5�������b.��@��4�{{{;{�u�/�~��IL��73�7aV���J�)��8���ofA8AWf���$/{���a� ��Z��V��x���A��GDU���!e���\/pzjn.f��*�o��)I�j=�R�Zo����@�?1����K��?���@p�]��Z+P�'~0��qi����n�a��Ei��f��7o��im�
QN��>iE�	Z����f������Q4p�b���\��~�+��u��r�Y+>!����;8�q��F��J5*e��E��L5�i-�1N��Q�w�w�����C�6��_���~����&��A�X;V�|^��JU��f�66����������F
f���O�dS���Z@�*6�q�����?c������V�] ������0,��o�y������8[s�qiq�| k�
0%�H��,����UM��#���F�,�t	�bb5�V��p���-��k���	v6:�|���V.�s���\<��.p�5&�o>�/�Y���
s��k��A��;���DM�����
S�=x�r��[�=n�OSp��y�������I��9��?�H��rl�^�[%��@���U������#�}�F��gG6��g��O{��QL{�=-�g���A�j}	���D���h��@8�l���9���)=}	���/x�|��e�I�A�p��eI�%:\�^�����9$�������.�)c8��Cs_�{�N����>'��oo�+��f����'�7����g���ow�2%�0�j�uuC�Ia�����?��O%��c!�8?9���6��8'������[h
�|��&�H]��8�q�F�(xcH:���w����8;^���Md8L����n�mn�W�{����k���~c?8;iTUu./qYKo�6@�y��A�2�>����Xt�lm����V�pJ>u������=�7�%�wA?�pj�q���f�!�^�%X�r��|��9|;w
�-\�f����~|2:]Ap�c�w���~��B�8�������$c��e��?���6���),��)]�������8���� �K���Ph�h_���������gm
4���x=
��j����E��� >����V���������-pY�����_.���5�u��,}����7�R�W�k��
�uE��o%������^c_�a_�I_�ocq���*{�}=�}=/}��w�67~������������8r���U��z�zQ�z����F��K��^��^��^�6}iq$N���/x}-r_�������8������r��|1��^����!�|=<���C�l�Mz'|-�� ��5�+�O��0�B8#v|t3|t3|t3�f� V�!E�[��(>�����U<9�QN�6	����AtfF�;���KUT�3��t�S����
�'��"�����'v��F1m�p��������	���,"6D%�x���E'�P�p�C�tx���8\� �!��/iE��[
^F|�����H��0�
,�Z|�+#�n����1��X|�#������/��X|��"�n����-��X|��"������+��X|����)/��P���[k��?�W��������}���~�W�E�%����F�b���v�r1�[`�Q������\��P� �SJuY,3g����2_���4#e�Ek����:�Ar�LW�J<�J!�J�J��J��J��J��J�Jd�JI<rq����of��l�<�H3���o%�a��qR�j�����
\������A���h�yz}2 G8��W�]8��R%F?N�O����!�l������U���.�]����V_���q����Y���~�c���������}�����4G���W�\�;��Wd���U��Bc��,�JA����j��v+D�;���S�W>6xS_��RDI�v�+K�s���{;0���{�;����m�����]�����5j,����5G�*�zV�����_CZ��)e����D�P*�YO��)���c��7�Q����e���W���r������&
��g�����[[oT`tn��R@?�1s�(�2
H����������������iW���N�G����v��W1�t��.���kZ���frB�?z���S����^�p�f�Y@*L�p?�W++�d��?�Z1:�Tst�&�6/���#=)�����(��mJ��8J�8*}�6xY���R�@��7U
�ds����d��e�?D�w���>��_�5����C�/?�>��IP��?��w����N�'�=�����p��Gy���s��Z�T����������rep�7�����P+\.�>��vnOLb���7�k[[;�] E���������	"3��)� �M������Q��W�I?h�i+lGi�%�]Iv
t���zcms��%��X����W6�7 ��|C������q�&���M����6��g������A�[9���_�K���-=w���_�������?��%�_�0~���(	��L�������=��$a'���E�Ke��A�����o76�o�S�cLc��5��E�qa�c��7�����g���!�D.p������V�����CY���aSW+�X�L*x��B�����F��#�s�qA�u_v���4@7T�y����w�� �\�/����+�7%8'
z�F�
�EQ���������k{7��
�mn_�i��T)�Z��9s���q�s����r~e���p"�8U���4O�G����[Y���]o�p��4%���Y����K�7��NO����)�#YV��.;8�a ;�&����u���
O1F{��SI�����&UUoB�U�f�ag���%S���7G
�Ws�7;5����,ny�3����g>b�[��0��)�s���g ��x>��6�
+A�J����d���A����R
`!�m�S�9���N�@�P��a'�+�/F��|��	m0��
`��Q?B�:�IP�I7Bhu0����f7�%�1��>�8:5`Z���@�)Z���F�}��^!��!���;R��pM���d��*etN��,;-��4����Ca$sg�++�����"d�`3��;<�p���V��)Y������
4�l�#���1%��Q��-�?��T�I�z���M��:'kK�X&n�����h��Z
�M������2A�gmO��g�H"���!�PN����C$�������-����������	�9by��J4���1���mU��3L^+��d�
�q/jW>���s�P=�oc������������U�P��k������y���a8��C�\��������
QN�Kl������"����U���]I'Q��[�D�IT]IY�l5��b{�uW����q�rY�	T�8�'^b�8J#�.1N`1��A�{�jM���x;�"J?�o����;�0������l���X}��y}vb�x��f�nF���r��N!�H���*Lc��Ca�.��K���4�����# �=����D�5�hV����g��n�#��.S���q� 9�2��k1w��@s�#N���Pth��[�?=��L5F�c~�K�R�uq5�]���%
a�����|�����UY�V5�x���i���QA
�u:8R+�����G�����9� 7'��k�!�Gro���i�~�"*�6��l���3��W�:��$t�Z4R�������������������]���x�f�5b��A�)����k��6L��<�A����I���|�i�.�v�n/9QD1H�Q�
�a^�n�I0X=����Ar0�8F&�z ��#Y�b��o�?X��^���U���Ui��I� 9��*v:wM	i������y�4�.Wucu$�
&������<�>�4��w�P^���x4�335��i���D�� nU����� 'D�p}g����q��W��67������v�^�����G�/I�ga���i6��vTf&H���T�Al�G��JG���O�w���A�����q����n�a�dm�S��[��������;��q�DH"BN&�}@$���L��� �%������j-x�����|���`������?��,���{��h�m�r�Qo����~�7�d�{:]�������o���lp.��W��
�K�%p'��r� �
u"�9!�-�w�8c�����Y�)I��(�X?B�8����5�/P�������m������|����q����\�}&��(���b�a^?���>�1+�D}��*:y�tH�aH���D��}vh������������������W�1~WC���`�Wm���7��F�.�cp����Lw�[r�$��
J7r��.� �O�� ���I4��h����{���%{�����7��^��8���E�|D�"!X�9��Rk=$]T$�� F�A�n������3$L�m9O
L9���a'�1@A���Vq�������D�G�6�`��`�le�v�c���A�2<�K����i�a�� �N�(>�C���F��E�3��A�I��	�I�nF�T4�F�l���!V����Ys����'����`jU=a
��X/���F��Q�q��gd��8�3`��-�"��8�n�)KsJ����[G���b���#SG~�T�O��$1[�W�[��D/;-<�O��h�c�:��":P��Pp
GZ�B������
��G�+�E���4L�X�`�F(�����W��id��V�?�5p���A�����;��&�� ,����|�l�]��91v���[v~�u��^��(,u��Q�lwS9����9�o��'��HG�'sHMW��*j}���IA"]�D�����h;3���S��������iiM����2JQ\tZFmQ(���B>�����L���yC����p:��'���v���H��bg�	��x��w@�@6���4j9�of����k{��\QfI�EpI����Md�c���j2�_���"J12�.��n��S(]j��hKxG|��m��,��~&F�i��E@y�6Lo����ZvR;�����g~�|�/���9<�����[~���z��?���~X��f���t��������+�i�1������`������zsw�qU���[k�7n�����~cos��:jc���3�r����u�a��z�����e?�+
R�5v��&���C�J�����>�W^������|��G������Z$^�EEIH�:����ST���D]D��~H^�y7�'���t�@�R,������I���wC`����+�y<��N��IO�m�8*z	2�(�O{��/���h
9��vcs���<���
�M��Y@?���E��B��X�dFLj�df�?�^$�F�����F�z��~�I���
�Lm�4$qw�
�4����$'g��3�
b���&wks���e���;�����+%�����& ��:M��o�q��d����A���^�#�=��
����l�
��?��R]��{����K�P��M�H�V	����#V�@�������Aj��K��h���M	X�	h�Ek
�����������d���I�x$���
�0g��9�tC�aIwht���'��P�(�J�n��d���������G�&u�a����������G��������(�2��z����1\S��S{)J�%6N������[��[��ak�(�V;d�R�"��V?9�D1x�_��o�xh���ozI��Mt�����^u���S�Zt��Qv\�oH���P
�E�����a:���8B��TK�	�G(�g�c"�f���\��j�}hZ��OzY
��#j� >�Q�x�]�R���{�)}����V�rfV>�2�/~@)��8��}�Q{JJ�r��c�3�����{`����)�}�r�'��L�0���0H�o@S'
+C�G&}	V���R���>"E���l�w+�BbF.Aoqf�4M�]��m�p{#���4��gb�B@b��A����S����������i�A6D�JJ$,Zc���&%��9��2�������b���-�Tw�0��x��U��t���:�x�	��a'��RX�8"������2��
����t�%���;t�f�%������3�dj�N�.��f�4�gQZ��+��d��:.:��GV7
���w�L@��%��Y4�����z�}��F�Nb6�I����i�&:����`���7�����?g��Grv�w�����?���K	�=�}���e���d�,�X����i���&����4�N�J;�xJ�W�,��)��zk���
��:���B0�'�2K���W��5������f�9����/����k%.��.n�I��2���!�p��5Mv�~B�
���=�S�b?�R2*��M����g7�m���iA�h��G'��"���~�'u����S������gy��>���>�Q.�e���J�l�:OWK�I�K������/����v�N|�@o�6&��!�"h��%�c!3c	��>C��*��vW�������"z�Z��ZN������6����&�,�FM�b��2��]|�&��X*H��D���%sO2M�0�'t�P�}���H&�x`H����NrT���r���Z0������N>�t�����V.�vz�Fx��32��k���;�W{8�#I�mZ2��#
<S�MHR�H��~�A���4�>v@��+�IBH�|��tg�I�B8/U����\A��3��Z��9)�]����N#�������EGRa��s��Y�Q�e�m6\g5��IOy! ��� ���`�E��������q'����~���M}=eL��� N����A�k�s	,�!_��O���W�n�drU<�HF��9>4��F��v5��3�'������!(X<.����u+i"I C������?G�Pw�fM������Y�QL=n��HThQ64����
��m�v��Z�\���h����UQH���(I>��(�N�����XLs|��A���B1o)�_�.n��ec��!`{C�@v���q&d��.�f�2�V
�#�A��v�&�88M�!�V� D�I������/V>�c+��SV�06pO'������;��cT7��c��j��� �m �mt�
�{Q�?Z���m��.�tP�.�5f�7�R�])�h�z@Eg��sem[��������i������e����Q��q7�����0u����|���Ju�2����D����S�}�9��p�*��u%���(�(o����a$8��F���~=����f
]���
������?��Fq3��wn�%��W�wt�1E����A�tZ�s�"�D����os�.������1k*���Y�|�����Xd�W������X���W���+�;[��m�Z����`?�"��v��9k�8�,-�	�y��`
Z�1��������V���W\�,/��l�;H���,�p�T]�XLIq�+i�[�X���e0[�����O�
rD:���i����A���!KA�rg�|k���=���}iCu%��]�G����P�1�%Y���wu���B�nn�Sx�:xH��;d
n� N��������=�B���/�L�jK�1��y��8���"�����4m�<�������k�lB
t�^�m�m}���W1Zs�7�B6��OSb���H�K������[k7��isg{���p|(mi6���
����vd��Sl�����C��WG}�>�^^���Re�X�xz��x;uiU���QrX��Uz�����R��^/�2�0T��u��I7��(��J}DT N��^tb�����h!g8�{�O��}��mn���o���-�� Kk>!tc�����!�Dw��L����	5���!3JM��~*�9����ox�p�Ma
Qa�X�-������J���S�����R�a���ei3Soq?c��Z�2m�J�AO��UQ#���2B���t����������V��N.\�����Z�.>C� �S�"����L(J�l"�;��%h�(����N>.�����r�`a�<�f���[m
�E��F���B���z�_���������GMEP�|8��,\F�x(��<R�Z���I7*�����=4���{��5��$u���)<8�����"���)w �m��������T���p���N<��M��
:��{��s�[����*v��)	5����^�B�X������G������{%a���3g��h��+�������h@mR�jw�'�&���*&�x]����lH�mN����NtH�P.��u����'T��$��p�����~���;���>�1-�)��M�m�"����#h'Q�nC-[�����G��E�z�O�|��B�y!>���!����N�@y3B;�2|��3�G����acU��(����v>D �>��\��~�
a@�EQ[�Dm��Y�`A�e�0��.I��E�������p����$b0��J���9AcbW
'�#���;�,��4�|����H�@�W������H�A!��F��H�����"twqc�l���(]M�/6�WO��r�^19��v�)��s����;������O!���	=���5�����Cwm���*�vo�����;�n�mo4w>�)���G��~AN&��+0�`�n��q��<'�/����(�J������$��d�j�w*3�������
M�O������z�C�	OlOR�[���l����u�m5�G�D+4���o�QtR_����N"�
(��V*����r�1��}�1����cS~�g�_�9��"�R��������<������4��DaYz��f�.���R&u�=����<�����������.�e����L1v�*�����Fy1���r�����pZ��7���>��
;��?��)��t�;;kh�{�r ��0��vU2+u�I�8�}8k
����c-}��b.��������s�6:�R�?$I-�F!&�w����:m���P��B���:�UK8�*�����1��$cKQ[�b
������u�KiJM�H����U,v2Om83A	��1,(0���l0R�H�h4�4l�4]��V�uNk:�	_;�K'�RbR7�Hvy��(ih��&�$��I��2�HI"[&�Uco���s@p2�� h���'�����������l��o9I����a��iI�,Z�%�2��"U�Y�q�p�V����tp/t�^H�N]�A���|�ME��������j����c�Sw')P0(�S�k��J{<�u�^;�
5���+H�B�\N�l�4�bm����7�uQ�A�*y����/��X:l�����F��-��s����Q(��`����U_��ve�C�s0�-3L�}0�Cz@��a��yE��A��p�����WBL���~�����7���h1�s�����l2�����3|��l��e0�<�R��Et�9��,c5U>#�DXk�f�L�+5.d�����,(3,IR�b��s4<��}e�,K��n��jP������1��L�1�����+v����aDtD=�b�#���������X��K�dwku���z��y&�R��Y�W�)�:�*tj��S�	�5�?>I 7��������B!����g�fh>��d���x��Q�o��������l8Yb���G�B����?��wG�vj=����$����sV�a�6�GM�KA��TY�&7�O��N���M�#�Utm����G�l��=��p�j��I��x�N��F��-��tM2����x|��k�1�j[���{,Q���j�k��i^c{�����Z~�����+����z��������M���9t�W�8�3������(�A8�3������k�y.2NQ����C�0-��i`��V6��������^��?	��/T�.>#�b�V.���#��'m�����Z����&y	���x���������D� ���JI������O�0������'8��ju����V����K}���MK�t����EP���F��}��\��|& �Qd}ndq7�����P^t�
Lw����R[�_�I����:{�ZdF;���p�����[�!���tXj��B8RV�v�i�{-:(*@��e�^�:q����j���':,�.���]�=&��,z�,p{t���=i0���Z
@�*�(�����������Z��u`��N��J��{�+k��������sqQ�t�����4���j��n���O�lnc�J������L�ZA�A>�%�12��v �r�P��E�S[U8���q�����=cD�%�o+Gv�<Q�FK|����0,d+n]�89�=^.���{|$
q�K�mi���v`��>,d���A��MR��f�s�+���J�S�J9uq��]N�tT(m`��;�~23�Zo-5~����I����~KF��������u���~��_x��������J�����~sF:{��������������&�a'"�E�v���:�A;���������3��Z�����~������1��/_��X��u����!�<�����-�_��D�W,U��\�7`�To��Nah?�)�1J��������)����2g�,����I�7:UR����ue�����������d7��������R
����g����k��f��t�{���'�0eU�k/pv���-�}Pq�e�`RmWST �s�9{GQ74������������D�Y�f���M�YMV��K�V�H,�#�Pn;����X�	&qA=��K6
�D���t������-T��1F����?Wr���/t�?�[�Wn(�
���9S��
eH�f�
�(
(�^���i22@>X2�.�P`�.���K����I���j�(-�$x���I�a���y��!������/��y���t�=|�zv��e|d}�A�����$�&=M�|Q���?,;��N��S��wT��>f�q��u+����:��p��6�I��~�h���M��u��x�!�Iy
�� i������hD�dGJ����u�K.��[��*W�B:QQ�LQC���V����Kd]�������9e�3*��$�b�������x��F���)=�R)�� :�Y� �����#��
{qd�}.$_TT��K�^�N���w����>�&/��?�I�^��/N�����(��eaQR����K�H�7vQ����.��9���k�f��������?�3�����Oon#�o�76��^,~�v�
-].���������cT�{xumoj�
MW-�Rn�fK�7v����SSKE�����e��s��U�m�s�!{bd�����GP���S���5���d�g��O�s�����t��oQmR:��f����b�(��2Q9��%w9Nd��E�<
��u�S��7����&�����P��KJ!IJ��I���t$��D�%�=�J�;���~O����C���u���&q]���3��o�������nQ�C����T���B��&M:d����n��Q�Dt��D!�%T3����c:�[8M�X���s�?��{��L��{���F]c�v�2R3~C5�W��5�su��:�&y<�}A�)�{�g��{�������7��������`G���4��nL)���6l�f��=K�{C�^_�U���%`dJ��o���p�
L�@b;i�%�@G�J<h�u��%/�5S��L�+�c�P���X_�,���L���S����vN0T���������c��$��A+���~q�c��{�$57i�B��X��p��`�9\j���
O�te�q��cIXsA��``dIX+�E�$ko��&���'5�,V���y�`���1W(?��G�$�	�QM����H4?�P;sTS����Ye��~��N@"��l'Y��ca���|�� �G������a������Pv��jA�'���DBQ�C�lt��sY�	 ]�}��l������W�������A.'.b�����v��\�kdb�Q��u
-\pf�����-� ���>��S�N�)�
W#'��|G�lt:����#������p�2�[3�������C�Zt��|��|�
/�K���8��l@�n��@��4�JO�� mm�5Q@���d~����(g�D6�
���Y�����-�������ZU�GM�j|�yD��$��d���
R�����gX�|���n������!��f��4c�}�O�|fU.�2��.l�Oo%�"}1���O�?������J:�RU�
���p ���CH�qH��oVg��)�_zjW��I?vCdY/��8���P�'�&-zJ�,�����X#�7�*�(��I���p5���4����"�)��s�Tj".�n�}7��Q���eI���:�����;��z��V���d��!�I��&I��C�Q�6�ij���R2MDvo����ix]�31U���uz���"��!�5��;���&����N��C���-��o���M����Y��jK���:��4 KG�3�FA�U��F�##<�������/��ME`���T���nq�
�@�Y6��� ������5������6�c�L���U�|��m[�3�p�{i��7/�s3����1�=�i�Nm1j����$�IF��g�����1K05��-�S���3�����F=�"%#P[ R�\��(�3]~� B��� x����Q>Eb���S����@����Q������lx<y@2L���)�4C��P�����3t�u+JAT����E`)$�hp�Q
z����K�x=�a��jt���"�:���l�7�SFb���N�������p�#�F�*��';p��%9t�<�q���i�� ;B���}�������K�:��9p�TbD�6�3u�
�)$�&N���5�����<"�0�X]a�Z���Z
.���P�}>�p#�RA� P�SM����C$�L~���jU.���8����"5�����a�&�����*�)Aj�Ne��t'�e���Ra��g�b���O��JD5�^T*i�O
&�����$N�����9s&x����m���Nf�S��d�f�������E�u��D�wY�����k�v�E�!������((t��;F{+wl�L��R�PhT���3�/<:������,���g�?�:�i2������0g���D�J$�v�w"�����D\�\?�&m�����Y��(M��b��X�3�w�q��]kO����%/>����#���l����r���.�ux�����gC�A�-�C�Y�ng�m?��&ZJ3^����9�3��v�a'���0����
\(��:�(��� Z\Qr�����~G�X���(dv�����{x(X����&*��p��I�� A)�y��b���T+��j�+z
��i`P=�GkBzen<�WnVN��|��^/k���XK{�j�>QZ��l�7��F��	>\  ]�Sf��q�tT��e�0�`����o���+��nfI���[��@�E�?�$�@oT{<�	��
I����w�Z�;��T=+�3��
�R�2�u\�,Pi�u�b��d��9S�H�N
p�jZ��P�\)���J���nU��V�W9��n�r�,\���n�lnT8u�)�����h�V<��r�w�=`���i5��sNM\
��n����Ve��Z��+��V�*���a���-���1ab3�
:��P�D?�@#OM�I����t�&�3! i?��/�LC�VN6Q\�N{e:�����(����	#4C��:Ik�r7��\Q?L9���9k�x���x�� 1
�|���Y����)Y�*���|�����]_oP2��L*���^2�������Rod������&��a?0��yt^@S��_r/������������g�;.P?�rF\��sU8+��C[�(���0r�_�LQl��)c�R�L�E�������x�@�T�f�Z�[���$��V����(H����?;�t����Z��������+T��������X��6�����PI���2T�X1�T�����������a�f��,���&sQ�j�|<`�K]�(���3P?����Cj�`t]�0�S?=PZ�VH����c,a���������p*��N��Q@��g���Vr��e�Qn���3��4.B�l������x��]UM�V�����&�e�g��J-���6v`�sE�����R�z��\�����YHs��Cr�5�)R	F�
��A{q+��iF������|�O"�6�c��M�0W���qy�L��5L��S$
�,b�~<����k+x	W���rg��o>�Q��T�u����s0�JA����<�}�������G)jN�10���%	������HXY/��Q�<�E�1�>w]�B���Iab8CG����!�P���j�D������SjU��Z��1(������BE�@=��LR?�PO�q�������|?�V?���[�s�%FN�f	�;��t����+a��I������p�$_"Y�%���$����q�%�,�Y �dt�\�U�@RQ���ONPQ�8r��1��bJDg�h��&�Y��R�UV ��d���������A��R[<'�(
��B��{���|�2]$�X�>����d�0�v?�m��~
e��J����d�g��8���6J�N��'���q�+.3E���%��N��]�"��XVQ�7L����2\�1�j2PKO�� *���C�0�YQ��������w�S�x������u�#�A�Y��xS���<��=V5cC�p������9���n=s"JR������O��?XP5�J�������]��0��K�NY�&���3������F]k*��6�����4�(70�,::�W�u����������|���4e/����+��Z +�5�����"���yj����$����mu.x`�h�It+<.��4�y6�W1�kE�������>�I��J����B�HKd���:V8W�����
=���R�To�E����D�Y-$bP[S��u��\��n8��<V���(��:���XyG���|�
*�V�R�$!U����W_k���������=�-�Q�A�l��K��^E�����M������f��R�����!��w������u{�nu��'�.�.��7�{g���(�pZ_�8S��M�s�b:\T�t�u�����p�u�"�� ��F���1��?�y1�������o��(�����f�+���G���/Te=/mo79���k5C�k����)��#Q-���>���(z�����')X��/�i��2#�s��C)a�qwI-���K`7W���(~���<�\���at�}I��oQ�@�(=�Y����PS3��^`�/�M}
VlZ�bo}����?m�-_�1j.9�$3�����X����^�0\S�M�mns�D������������-C�����G�e���`��Kk�S�gl���-�A�V�qq0�tz����i�����_����VH�fcsm��JV��olV#�e}#�6o��ol��=�D?O�v�����o�����>��j��
��-��*��*��8�cr��s�����\���Q��=�R0����T�C�2`�{C�ikfG������k�"T��*$���)]0q=���am�kBw�@���K��(�C�h|���%���k{/z����e����
�t
�����E#!����xy���JF�+"�7�"Cd��$w���������x;[�u����4��B��n)^����Y-!3����"��b��tsZ�\(A�;=���bm�z��1O��++3�f�i��]�t����
�tt�8x��5z��f�
u�+���W�^�Wo��v�
���
��g'�����m���6�u�RX�%�|�V(��F���*���4�\a������(�ln�)�G�����
I/�kGQ��_A���]�����z�u������}��D!��v^5'�-�����9q�b�����26���,=���2X���"��3:����JIw�\eM����o������^����"b%�D�~�V�A�a�xv�O�]8����.����>]��{���T�j6��s����#�q/�������)��Sk5xum�|U�~�4��]C�G�����u���f�=��Q�[`�{�G��*Z��-�D��Tpbp�����{�R���g�G����R) r�	�	)nP��F�x��tP���%�,ex��2�CzA�`�Z	>��V+�
��I�H)���nk�U��Q:�g1PL{��(���mS��$k�,����S	�@%f�����W�b�I���E��0����UT�g��+N���RZ,��E���Ng�@E�a�kC%�rU^����ST@-N����.�P�N9\x�����M��U�"�Br\U(��ZH�Vt����S���N�SJr39�U~f	��
&��linTN�����(���W]�R��$�gz�d��1\��1���c�o��Q)\�8{���MQL^f��V�����evAm�X���e����;������H�y�c��x$T	:L�8���F�+��K�vY���q��Y(�D(w� ����R�qUL��9i,������z�����PO�������j
)��8�kp�|>�F��:g d��KN8���H6;�Q���VYA�g���������e��
�+U>�<Q0:�
B�nzV�?�����;�%�';�,�kS��0�kX��U��XI0�f��Y�|J\MV=h�~��6��a�2���S����3������;v��N�G������6�)��nn���+��p��P�=��_�S�e�1�o����u
��{Q1�&�c^a�<�ey~I�^fS6�&���g�/��
�Pb�e�
s���������i��WK�����67jy'��jEU��.��G���YL<i�F���{B%��I�=�'�ER�l=��Qu��A�c�����K'��|������'���~Sv�+|��6rwb�4x�@� ����`�T�b%
r�3w
��'kz��ZMn
A�S��8]U�9*�cp�YO����;_IH8~�zjj���<�l� ����Wv&l��[����X^������.���W����,,r�q�+VP�����e����[Ze��r��b�����*�#\u�I��&��e�V	X�IqvG�O
gvD� 7 z7t=�c@��5Jrg��*���;yu>*�;��

���@1T��4e���x���){
�6Z;"S�W	���q�s��������;��}���|���{�tT�����y��B���eN�az��`�6�"5����m���'���t��)k0d�����w�3�G�W]s��#���������{Y�������R��]���VJ�}�O�U�*���^����/N��('L��>��G������>��gw="|���e~���G�>��Z~�b}{s�������3���^��0B������]w���<����KC��o>+18��ac��U��vym������E�� 
���o���[B��Y�u��A!��2+�ux�Ds)�_F��oG�����/IoMb������}�����!6��
vS$W"+�7�X�s�w�����SS�`c��k�����82�{��,�Ba+���h��da������,U=ZO�f-R���]�j���D.����w��8��w�1��sa��Y����c��g�c��|e(�SFI�x���ll���75��A�i�w�*VE���=v�am�������')��D<�O���I 5���$�b_Q�����h�A��������-B���f��
�?d5@�;%�[M����Z���I%�xDag0��v��2\��2�T%�,(�FY(��`��z��`3]�f���U�5*VB�j�ZV��a�\�g��S�[�7���nm�o6��kj/�fJ6�{��6����tZ��>A4�(ln�4�+��K�u���i}_�#}�"�Fo��6Zi��1��F��1_��s��U������
����%�q���H�3<�`	[������E	�}��a����\�����,RF���\�*%{���R�U�� @��{����zP�n+�Q�N�8�
R.N��Z4�f�����F}��q�Z����}��b�C����K�l��~k4rS��3�7�}so����(�Q�x�
5a���O�4������)��[k�
5����|�5j������@�}*Pu�l�
x4���l�,�p7���Z��g,�0��MU�����u�����lg���7l�b�6	Q��O����;���*��:3�B���*����:8h0hz6�>EfiN�y?7�Y��C�.+0��N��DD�q�l/�,j���������A'���&������_v�g�>�Z��]R<qj��7K$Ti��R�btf��][wIo�g�8��Z�����m���S],�Wy��p�UB�����z��\����4CQ�`3�	��B,�#hg�B������,7���WX2�����J���ru&EJ�)3������[\��01��G�yz.N��5��K���rRA�z|Z�/�1��YIQ�L����p��5F��7��-z)*��tB����K"��=��������� 6��N)����������S3k[X��D0??�mlV����g��f��>3�|Gy��uU�u��t�S���/��F�����q������Dg��q3zb�+��,{Z>,������1v����Hi����;F��������d�L5|<��5��M5X����)=2%�Aj�4~"���m9c���W�����Rc���@����Q�*f�������^j�<J�t��=;>�L_��U�kh�]%mz�U�iA<�8K�Pff�q@���+��4������)/	��zH��q��C�N�o�	GD
���O�?�c[�o�WR�#
F���Q�������}J��(�g�,��jX}R��5�J�'����=\'�o�����z�N�Gh���c��I��V���Xg
�j��!f�������?!��`�Cu�-��Op����O|6q�o��VBtC2��vQ Vt����}��\�$C�yk��~����Y\�d���^�rF4@S�(��Z�0�X�����������������|�����Z{�����V�qH��nK��Z0����$�?�G����/�g_�l�'��;�MNG���w���	�f|)B��1���\����9UL�Y�>r@����t��J�{u�cs���O�f��������6������=�����^�����F������%bo����W�s��-�a������h�E�UMvp�����-�.D�v��=��S�sz�-������4��/��w�6o��}z��{������g6��b�q�>���O�c�"��{<�*.6#yv.�|��O��	>���Z*���
���k�'�F�"��x��?���K ���D���L���f�W��rS�f[��AD[��8�Od��=�SEDX�=�/c�5���������e]�P��Z�h��~�������j��^�aJ9|�,{	*��Q>B����2�~4���"��q���q>b���W�6�{F�������?���d:`�^��c��i��>B���'`��������{��;q�9�0�V�{;��j^��ge��e�����n�.unn.hQFpJ�
I&,���aiw�����N^&k�e��
�>	�n��_��S
��Fw(<�)m�[u�}g�����q�D�O��
);q\@�U���PB��}^(!�8e��F�{e	����f�\�����;�����=��{�W����������rZi�h���m�}�2K,.�C��z
�\��K���$iQ/��� ��p&�`m{#����3j&�u^D%����1�'y�V��9��mH�>�<���8=�e��LT��.3�����'kK��K�v9@��(���71K�NO�;]oZ����
�`o��H��#��D��a
s�>��s1�|��S0��t������Ox��[�&,���\�Z�W��[>{�CAg6A_�����������k�li���$��sfq��&5���&������UYY[J16{��P�6<���������V&�����<"�;R�$����C�
lb��g_�#*�o�[o��(�*f�
7D$6C4�D*��:����gb�y��,e�gTHP�D3J%"�H:�^�J316�E1���6��B������\,0��Q��0?�����w�����Kc�^����gg�nQ:�����1���n�f����r��(kh�N�7"��J�������;�5�Yqo������mmn�)oJ�N��t�����,Q2�l���|�Q��S�n8����,�'�J��tZ���(A�����E\�]������:�����H��g�Q�����/k�� 9�?���W����U�w�Su����D���
�'��b�X&���	a:��b��Eq���D�^gR�:�2ZA����������*��
aj���� ��3�N�y���S��QA1��!2�T�4�U�������j�5OQ�V;Y��K���1���J�{��P�j��HV����HO����0���D�Fs��N���[7!^nd(J7���!3{a��`'�����uN�ET�<H�Q�kCI:��m�U��������c:)X9����hN2�WHy�D��X��pv�D��c/ �����m��zrK�1+�%oi�����(�]�W�.�%�uq�+�:[���<��q�������{���&���24�Y���5�3f�jN+?X"�6���&�����T�$��
k�C�����!PX��E�Y����f������|7~��9��|!�
;�:� �r��r(��R�p���L z3�����	����B�|A��dd�n��L�+���0o<��Q�
�5P&�A�#��F�b�_T��y�����R�����e�*D�(���q_�]z�r����7<V���qq�����
wg�h���t�E�~�zA�7��KT�N���1^h�<HHx�������n09)�����JM�|����}_��~�2�g�gz�])�%�9��t��q��R]�����+���/�M�@Q(\��Y��(eePYtpJ�3K����������T�aa*�L��5;���%'=����^uv�u��Y���,taF��d
���S�/Q��t2�av�^4)C��6_Cq�o��l��*���!r�J�5�0��V}�#9���%�������E��d�e���bP�����^~z������D�k�s��>�UPr����l�X�YX��z��`����s��F�>P�r"�5�!�)���`�����UV�Rj�"��6V�
�C�kq!d�&�<+�g�������Sakk�%	##�c����������-��Qc��1���WJ�:u�jw��p�.	#��a
|�����U=<��z�����F���(�k��3���3�yG������M#�����������Ea���=����;���eP���R;���B��m$�U��P��\�/��u���%��<'z��,�#?w�YYup��z/��+pUfe�UfmV������-���y������!9zXLf�BE�T[�Ufc)�%�
���
H,X����l��������]�h��l���-�l��QeF��E7qY���G�a�
���l���#��$.
bL�%
5�w����Q/2CLX��(���6���Y������b�B|H��JG{��mj2fR�j�9,��x�������q�G������������(z`�nB0//[��X���+�fa�� p��]�X@r���(�������kG|��u~Q�����IS�1(�60���T2Sc/&QWTA�
q'� +0������A��~�
Od}�QM����!"��lP�'3#����e���k��&��T%����C�U�BjH����6��u�k�?�&lG�O�N�����[����_��m����5�|�6% 4�}^r����2�
O�1�+42WA�yk/#�z���R��}�Z�R"0��J�j���$Rdv�kmH�gI�h�dU� i�zf��sdL��Y�fJ�H�s&�a}@�R#�p��\��������Do�r����f����`�0<���2�
[��b����v��V��t��**-?1����q�"���
('#(��~��M����������I��B�<�<�o�V����
+���=�u�j��%�>M���FNy��F����kL�d��aG%�F����������K�����F�$~~6=�r:_���X�r�aUDA{w$mLZ�H�r�
������)���'�;|_bOTpgfmm28�z�D��|x��KB��?���B��9�T��L�P��?l��0�X�	�y��-r�*��X���0���_Vu����_����u}�
�L���:�R�������)�g�fZ��t� (��m��{S�L���2��)��������yZ���K��<;���6	VAS�~-������NU?����hR����6��+���7��O��}!DD
����8:DG��R��aK�-(��X��c�>fT��:2Sp�`����o��^/~<b����7LO�6�f��j��jn���R����$<M������������@2�����������[[e�3T|;����1�F�n�����Q��SAKY�u���*���
K��N���Y� ��������7
i���;���C�j��n���g{�e��)�=���u���^���t��u�A�u>(�a�XMWn�8�?tQ��=�\cm��;����C��n�%�v�Xx ���`��zs�������2��A�������v����?Y�������rQ�l�I8����4j�=I���{�
�n��fO��@#��T*�K�wq�L*9$8�Yap��N�[���P!R:m7�{�k[���4����|�:
�M����a���)�z�����<u5�_G�z���7���l�I����~����}�Mbq�0{;w��a�[=������*��N�����s~Q�@s��G&��>�=Q���'V�]h���<���;S�U{Oa\������Y��
�I�����f}�~��JH"=
0/��j���yKZ,���^����o?��9�`�r#]"7����n���[�OXF��C����P�U	k��A��sK�W�^8@
19�]Z������w(��������)�.�V����n�#�'�����\�o�P�Y[�=L��D�Q�o�:��&����Y�������������������gg�����t4v;�E����������K�>^r7n��zx�f�mvks�1�^�H��IGZ7������.�,�j�0�R�V��@e@�i'�!�]����v0�����f?����I�Ds���E���i��UD}Z))�9<9����A+E �im_*M��:��cy�4E��d��<z�T�����N��Hq#W}\���Xt�[�9����D8��#��VX������yy���v>��G��c��P�^�haI|=W7��� :��C��n1$����L�>|u��=����B�	��Afde��2�Eac�qIg�;�7E]�"�p�t�=4g~O��{3�]��<�KY����x��5�o�18��R��iG�o�Cw�N1[|���7g�F���#�U�9NV�9�mf�w�?��������{e��)��w��NW�Z4R��@�^����q*P>��1��qT�SS�Q�����oG���Iq�+�G�����JV�s�d��T�����U��:����?�c�$;-D��8�Q���
���/ykD_y-�����(���#d�Pd5�#O[�9n�T�>�gE�P�6���aO�:�h�F,�g��<�GXV�ej�:�f*��`�V�x2��e�^��k�k��������J�����
i�v���	iB����6�`�J
$���?;��QEYe�O3�C����,r5@�&G8����2Nl 1����,A�"�)��%IV�cm��\]��P����
��IG��y�,p���4.|jA�`�2�#��|Mj��<���w�����J�1M�L6
��3���p��]�>�dY���/��fK�Dvv.>~*���v����=@���zR�����P7�%F	����!L���N�
���B��`&B#�#����5���7����@__W�P(	�yQ�N����4���O|�b��X���pR���G�~��pdh����C.!�YaF��)�l�^x���4�/�H���QD
~��!;a��Z��[�=���2��[�+3�B����{���zF-�<��������:��n
H��;Hc��{f��r!a��$�=�|�u`�o2�r�����y���v�N�S��F���m�vr�O~pS\D���}���+p���(��������n=���������w���{�4\"$�I6E��������`�g��zG�qn����\�����\�
AH�0��1�*�M�F����s<�����?��l+���7Sr2�:#���R7|=)6��K���)�B���@B e��
������&UoV�_���`RJ�;�����E��s�X�_aot7�����������|�����iL���X8I'�gaV�B�2�tk��d[W�q'��9zN��UZjEL�98�>�:�L@/��)�#b�	D�o���U����@d/Tcw�n�s<6k��y?j�����:�p����Y�\�X2
U ((����F��BS��n�*T{F�4���Z}��4���H��hH|�_E����F �\�0TYp�����P	"�����,��������p��d|Y��N��8���w�aw��#�L�@+��8�bZ����!�z��]��wq���=m�Z)����/�v_���a3��'���������b�4����8������gp:5���3�y.*	���-��&�T����fc���Fi��:������q�������D1��O�C1H G@w�O��Y��������qN 9@��QO[��k��Qr�����e1��S[;�C�tQ�5�c�,�����`��v�]<��R���{�}��p�e�w����������O���p^�e�a����igN@q��7)L��0����N��lQ*�jP9v�=Jx�&8K���k��X��SW���������Z��T!���'�g��P�r�zUVv�[�r:����A0����}Wm�X-��a���&&plE�(���.�����2�A�h�P��D[����V@�J��'C`��H�F|O�4��
�W�A�*W���`���!t`�OsJ3�����������`�J��#\�K\�L���bu~�8�P�EW3�_a�*3j�Y�s1]��-}����X�� d�D8d=D
�mt��4������t|��
,V$�`�����]
L�����F�N����H��Iv�����]��P�U���WGw��m�V�����C��D���H���8������0;!�3�Y�]�?enk��^�����{��.�0(z���I/������(�@�+�"���K����������'<��F��l���]IA��^��1��G���Nis�x�a<�����}B?���� ��dQ�������6��� �T�V�E�t������d�L4L:��~�s������6��Q7�p2���)��CQ�����}��T$������")M���@��Q��dB(�`6�� �P��6�{���k�C�+����T<7JO��k��!&��4����N�1c�{I<�N�
��afm��QV4�p�q�Js������u<��O�_6/�J)�-���������[�y������wN�zT�: ��J'���c-��[��k��`�8)N�}�c���(����PBuR�c|D��!��$K�����Q"�AH*B�A����S
�Y���X+`_L�1j*�9
�j���EYN�t�"	�FE�-y��RC!�Z:<���~	.A\XF9�c����c�w�J3��SBL������%��(0�S��ak��,��'���uFJ��!�+�B�R��m�-���LF�J�����b������uT���f������]0�.��
>6%��By��8���M`&��X�h����t����(��z�z���&	��6����y�����	�Uz���Y���%_���9p��)@����4����HT��D"�)�o��8>���i0�D|Hg73���&�����G	.������lke�2W	����M�v�H���r\�����-����j��E]11����RhV��DZ]�Za#�,�M<��2"]��czT� �e;��	6������z!�L�u���h~j+������[	l�iC�l������+U��L'�����/ip��G-6�2�����iGu�Z���k�F���j�g�T!��X-���U�2���a ��v���O2��\���#2��h ��(kz���������J�_M��Cm���O���)g�:q����7�����,���X�0����!�U�	UR��*�
�0��h�6��*N@�6��P7
!1�
���JM���,*�MY��)���R��[��<�^��@��d���o7�]������wBOF���E�|4>ym�����������&Q*j�dQQ8�p��G�����B���B��@nEH+��XF#���^��/+[!����4r�mG��@@8da�F[�������V��Y���3���9��7�N�o��e�D�Ab�J����`���RrH$/�s@9��5c�@�1A���-�C��v��P��At/��s�'rvK������<!K�L|A[�M��Dd�������wb��Jl��2�j�_���o�<�7����1�i����FH����;���YJ��%��\I</B*q��oh��*Dm@LnM��x��v��z��JP���x��>`\�kw��2f*�M�j�������4�=��TI{]�Bh��Z����nt���'}GPpg�b&��b�g����Q��� �U��
J�����S!���'�j��C'���:E.'#��u���JS�&M��	�S�&�A�CjC=
hx���=�f9>���K���~�����E�5
Km
�p��R�Ra�@�v����$u�S�2Y�?A
�u���:m��X/L�N��_D���sC��b#���������jeb���E������%������P���?C�(��f�5T"C��k�wUn���U����QN�e2�J9��>;��HK)�t��	�n�y]o'�a����'�i�P�D��,�Z/5�,�{,��y���������50U�h!@�w{�������$��r��D'�8�,W��w{�~���"D�O��L����

&�����N��5�G�l����$��L�L���I��Z�E���Za�'�Q��46�h�R�|2�a����U2�0[����2���L�`����o�cxw�y�!
�`F���adi�68���hh�o�;'O���=e��N��l�t�+|R��$-�����\���O+g����&���E��S���pR�='�����d>�x\��L���^�3�'En�qs������ 'wF�����*��D;c�3��&���+�R�4�8VBd��`��+u
��S��S*N�;"�O���~y
8u��V��q�z&_ShD1�~~v��7Z2#�P�%�X�Q��&L�C
H;T\��q����.�fh�������H�^�����������|zy��������Q���t&\0�S_3�3��:`���zs{����������o���*C&�[�d�)pd��G�o
�����Z!P-P�Qu&��5��R���<�e���.xD^�Y�U+WT�����tQ�OK���\����������R��@F6��"��NOI>���M%��6X8CM���l��@5��dt�@��W�a�/_�!iAE�i�}a��>�dy��NO�{�i��H4)OH��+V��Y��������@�'�W
#da9+�v�A���������4��}T��.�xy&���
�Wm��T����R���49�����hFW��Br�2�9���bUv1�n�6{cv�����[pP:'+����y��5��L����.e��p'�AG}��� ����a6Tn��v�GR��I�:�����8|�p�������v���#dr���'9�@P��5dBKn����0�������'��*���u1��mR
S���k��KF��?U����4�K�K[>�����h��������;b�&��:�,9c���.q�������� Ha�gX�^�e�i�4"�6��K�y���c"/�/\-��:n3M3��:�@Q���q
8����9I���+M���6v��:�=_�r�1��X@��P*g���f;tZ��y�\>%���#
[j������*� �!R����
�|�����q�6�T��,�%^�L`SVz���r'�/_}uu��X<ne�(�	�����7�������� ��e��������I�n��8�y"?%����B,�D$#]�6(���F��t)3h�+��*�I+d+^�������U�?�N���`]��JkJ����;{�#y:GwQ�-����N�0J	.��	.��A2�T��4�V����U�)�g�f����0�%�����m�t���6W������-�*��1�e����Eg���bRcnE�0��A�E��(�dF�1N�+�������S��h���T�=y�I�D�_�����I�����[2��A��P�@�DG�����e��\k/�
��-H���������������������/@��\=���r��\&Wz��B��� ���"��H��W^�b������K��P� �h./��P�����6�����%A��2"�#@#/Z��A�|����$;�$��NNiA�)�+'��$�b�
'���k��Z�v�jG�^H��?��-�Vt�'��.,lv�t7Z�-�)�n����]CmW�`�V�<!4r�9\��W��5���a�RY��>K�a}��'&��9�a������9��e�zY���	6������
p8}���q4�v��aO���d0��}'%���2\����(���|u�}��m����H�1$��$,�< W���GenL�Sy9w���	$zS&��	��uFd����5�u���������!lx �j�N�)���!�w+[�=�NKk�bH�w��)Y�$;���Ae��wt�"�5�������3��$�1�������m��**�y�f�W.5�i��Y��Y��#_��$@��c>���(�YMz�tnE+��<�\PG���0�0h��S,]<���4����[��Vg�#":B!;b�$��l���'���c���	������,���,3�$#)�����Tl5u������"��H��J�iM�8L���p��������"��w��|�N�����X��wQ��9�(��1G�������U|~�S���*_���?&
���R>�81O�XT���f�3|S�H*Om4��,+�u4Q�R-j
VJ4S�e��c#�M����@�}Df��r�d{����������
z�YP����O�������{RPW�2j�������E�:�9��h�[Eg��W2�r,kt&�������*��~��O��������������{ ��Ov��/��'�(����,��uT��b5
��i��� 
q���@n�f�nF�c��&^��E(�6L�����v?I9����d�q*�${��Tk�F�����><�7X��U��t�^�Jp����(��,0�e��#w*22�eZEF1�6y�Vnoo�l/PU�}����k��]Q��L,�5�`go����i��lm��lH$,y�b����Ra���b���+P���\8#=|�8_������+��y�S��N�/�,1:�@�'-�5�c���i�r�ee�d�~�m��������{l�%�L��)���5�������s>����hM�����n���i~:�<�jV���}z}f����9<��-�����c��`���������U]��n��)]�8o����ass��(
T�g��~+���\S����V5�E����[��j2�b����;{���o����Q�|�$F���z2�M�-o��$'���]�2��9W��j\�{�0��M.��iF6h�Z0��*B-';U�LfHYK��*����6����N6_���Z�xF����H��|����h��=T����u7�m��W����Qp��Ng#�6���;k�
��q��/(b�J��<���_�us6��3��@'�/�|�*�b�������/��vn����Wk�6I�ZR_�}CT����H�~:����\���T)2^sH;���n�q�8��
��t$%
�S���{�z���T,��%�&�� ��'q���,]��;��
\�����$]�r�`�*E���C$f���<����=j�(�9�K-���z;�vf"��3oP:�NnIN.�0�T�N��qU��B����j�V����e�c��`N�[O0�����������@�������\P;F^�U�
�������i��:��w��fCI�3�.�\��<,��cxc����L�z�=�h���,�Y�#�Q�Dh/��('=w���E��������+���0��x��8gHL��$jT�s]�r0?#���WW"|8�ED�����S������d&'q��5}�2�.'��_�~<���>���aAq�7�vn�N �Yh}�E���N�����5-Mr��h���tNt8K�&3�lM�d��m���W7�o��Nv�)�C;��H�(7��L�~w���;��Z�������o��yEZ�N��:��L+������b��a*Am:��d>�{�� B�L��dC6���p4K|Q�0����8i	g��g��<�9&6��pE��T�g��g$��Y�e�LX�3k��{w��0,��G=t����d:��3��y�����r������{5(�p�����`2T���W������@N��$�-���	�������O�����a��c~\�o�:N��az�$cGE�PL �A������k�5��DpZ\���G�T$����Yr����X�b��f��Jq4���nE�DH@�wa�J�c���*'��F$���g����BM��F��3����5��p��[��T)V*�2 ���b���[��[y=�v[���dRjW3���f;\H�vcm��{�/��8]��������+������p@n�������$�B]2%�n�Om���$�!zb�����_�i�u�d2�z1PV1AL��������n�UH��)1fO�S��
vT���?�E���(}j��T������K*`����Z�-��I�"d%X���_r��b.X�=�5�m�P�J�0���F�&��2� x��3�64@���\���H�U�����#;���R�Ff�?Z���M-P��x�r��b{��}��(�
�@�A��Cj�YtA������t�I��J��B����j�����S�aJ*%}QZH�$	t�R����7��d�-mc�'��t����f)Wk���������<2fd�t�<`�������V��������=+����L�P���q}T���,�p5�s�,L>Q����	,��6}?�j=��<�����{�qzz�/�[��TA�g���.��j�Z�I1*�[�DF=g��mv���"���/��s��r��wf��G�[O�Y2&��(<]Y�$�?)�)I�[���?Q�b�=��X�]�]�&�N���Om�\���^T57�s���}M����@�J���������16�s �|��Xw�:s�s�M�n�R,���$G���*��epn#�_����MA���i���DsO���@y�R�o����-_�)������Ft�G�X+��6�@w���7x��o�d������q#)�i����V��Fx�7U�rCFm��x�AX ��(�T��5T��y#�+�]�������(��l��Q��0�	�.U���U�X&�}uU@����x��
�J��f�p��v��� sV���I-�����C�8Z���"s1@o�T"�� �["Y��Xv���s��{�Z(���v��eh*GRQI���,Z-��
�::������#���4,�E�D`L�ZA��T��R�+6��=�TA0�T����d�lq��|������/�R{9�x�������ia�pi*A���*2-���p�����|��a{�(�*�M�y�SV�~^d�����VC�la���q����E�Q�Q����������]�8>�@�la)E�L�}.C�
�g���J��X��U��P�u=RT���&��W#Q)�J�#������B�8����S��~�*����	���xY��~��k\8\���aV+���$�;�s���b
n�:�O�����o��[����^����=�7.,�eV��j8���f�W�(Z;���)�m�<���F�
���E}S6��tP����~p}so���n���<r�Y^w{}F+%�/�R�Y�[x�<+�Z�0nn������I��r
Y8��/�[1*xo�Vi,�F�B�N�����/l���)�wP�H9V7��){�X�8}2��c�v�{I����)7��[���iHN"�`fo%uPA����1����#hNB�����tB���3����r�b���)�����]�������XFK�;�q~��O�HFp*��4(�+�pF����M�L��K��A)L��q�Hc��\�}��=�#��N!`��'@��B���AtH�.��22��������	4X���'�U���";'�)*����|��H�E<��&���9dU�'{��S��(��s<L�Y������z���2�I�`��^*���2b{i���E�.>������cg�o[I?��;��Im�	)����T�p9`v
����i�z�qWq�6���h����g��{k��;����� ���no5b����}�>>�+5L�D0I�;w�'�Zn(�H�&�}��^�7�����������=�7�B4oo��s�d�	��z}8��c����a��������`5~Pi�!#p���3Y6W�P��H�3��F��9&����y�������sg�u����shz��p�PNr&����f4A���
��������8���g��3
�Q���w�����������g_���gEa+�AO��|4��5x����6��1�$���tE���M�EZ�����zss|��3j��y�i��|gO��y���\r��K=���b�E�&�1�� �Ex�����)�&��xL����x4Tq�xW!P����t�����9����O�e$'{�(�%\B��u���zR���hJ��Nd��[��Z$�����~�S��{�����u��U9f��,K0�}�T��P>�Z��X,��Mf����Dz}F�Cr���A@=&l�X|w4g,��i��&��A5��#a.�Igs����Q �]���N�MuOg��yb	]+�^}k
�m��88�d	��4�h�zI^��584���>��9eDW'6�A#'�������9�H�m��5��������H�@Q���X%G5����o�����	]�C�=����x>�#T��]��4����9$���|i��;����7���;X�o@
S�(���X���Q����o*��|��V-��������.�-[9%�����?lo�i�C�;��/��L������+H�
��VZ#��v���z1�&W�e_\�{�J��7�u���q��
j��6�YvM3m��E��a@J$���d�7ty��(p��v�N��B��eZ���.O����0I���m��mT���i+H�vAV�� �e� ���H���7�;zT�����������)�wZ4q�0����sk�G-��L���.��m�F���U��y��~us������#����4��)��uF�Y����������	���m=g+����z`,�(k8��J���A��F1�g
�[.��X�bR��;�����3q�	M�:�.�q+����f����XkW��%�9��
H��/&�2���"���X(&>��f�\�@Jv�_�0���<�x����rj*�:5.
d@iv��]N�R=;oA����(�+YQ�t�g��U�U~�=��F�,�����8k�����Ju�'�|N��)?�F?�F-nT��&c�
�K��U��>���,��bs_��2_��aN��9�$��g�Mr���������P���U��1�����V�!��Z�)���?��2��S�\�<i��)g��r�U.�8:� Qd�r�����7><��}�4�u32�t�T��y0�7v�|#���l^���3���l�m��sk��~x�I���x���C��< ,hZG�t�W�s�:��f(�����,E��*�����Cz)�\�1g�J`�T�bQW�nwj��n����&�A�tN�k�f�m���	S��W��� `��p�r=VU�5b6�l@�t:�S�n����>��K���B��
mf��g���V�%�� �DR��.�R)#
��']c�6������W����p�T��Y�Y��m��W���S�~F��g�*T�����(e��4�a��;��`g��i'�������d�,��j]O���Z�l���(O����r<��L�c����%.?g)�Z�`4gd�O���~d�h���m;uh>	��'��
I���L{6�&�������N-��t�[���AfL|3!��<jzx:HAW������l���ljCUX��%U��hF4O*#����LX��^����R��0���<:;��=��n��
5(�|;>���v�x�q��5O2��w��%���G/���`����^��l�q-p��5BHla	>Hr*=<p��^�w���6q%�"&����XUd��m����i������`g����N�.�(5&�9�qu��i����������������U��?c�sj�3z��f��I�����
�M�i
G�6�T��m��0�{�P]cq�~O�$��0���)��;����'�<\����^��t$
�����U3���*�������G����jA������e��#����~��cG,cZ���P�������b��Cg��S5m��}7$im)�u������d�����;�c�uy��6.IR�w��Ry����OS`T;�Pa�0>�e�}d���z���t���;US�-��)=��u�*{q�Z��R�[�"HM%�C�T��� �M7K�rJ�H����b?a�"��p���:������������)�3��j3�h
*=��w�Y������C�Fh�J������G��l��	%Qd�+�y�v���,Y$�c�\�ds8#�:1�^���F]���r�a��"��7�-'����#���@�Y�����=?3��vY-*�A��*���7U�T8��2�����X��7|�U,!�S�����Q��:������z-�����i��E�,	�&�D	ux
�hP+9��v��B�j�����ku�����8a��dO<�LB�	-y�&���z.]f��+�~��h0H�]I�����[���Q�{�7zoT[kwpg{e5�a���~�c�����[�	Xg�`L$�����\rP�ar�����HJ���K�3u�-��!�zc�^H���$�Td�"�Li��~��k�}8��='=��@}��!��7:|����^�TN��u�H�N���$M����.�9;1�Y���]#���{�T��.Ge)2��^����J^sE<�f>&���?5��S�RzE:
\/�]��,�J[k��[�R�UA\�@8YXQ
9<$6����WeG�P,A���{����U<��C��&6�Z���K�='���]��"���y�����T��P$��/���u��YQ|��X����2�b����������l�ZE��k-���Dw?���6��#C���Q{��#�z�B��B�`t�4��"�mRC����Y��G��I0e�<r���4Q���Y����:m���oUQU'c8��������U)��4(�o";�%i�Z��Yb���Sk��* fu26\>o��S>�)�1��l�8�d^��X���GND}��8��5������VX������!������.q8�Ig��P�T���0t��R��t�E>A���x�F7?���I:�������mP��}�P`keX��T�HH1-���N|�7j������bh��j)4�&����^�4�j%�R���0�Kr~�YE])Q�������2�Vzm��G�t�?�{0egb(�)�L#s��A�Y����5�Ho��1(��[{����^Ub�����h��ce]�V�P��]����y��N	d����4�<!c���>���VrI����^rB���b��%�JAy]@���,:��1�'��%d��B���t�u��MR�RU�W7s�m����?=��������J����M����C�6��(n&���.������0��62+�TU���G��n�K�A?���R{@6�p�;��x[��TE��i�W�@9{5��C��mpL[H��������<��9���.y�����
�+Z��C
���J"	��C��\���������}��d�����Z�i����[�4�&m�]u��0�Ze/���-T�N��DxZ�O��Q��
k�rf��I����s�Ea��R���(�d�����h��r��1U�i��r>FO�1'u�\�~��rr��Au����4s�	~����H������xq�
��Xm ihS]���=%@���nK�Q&�P=n�dBLHj`�P�E5�sdB l�R��*����,�fo��4,��p�k�:1r�AlG!i�8����F��N���>�����K?���jR�����fZ������[U�-'
�B)������Z��T]g�����jU������(8fGN4"*Eg��!�2Ze��;����9�ve�QT7t��RP�j�B��$��`o$vNlB�%mM#N�����9�����-��W���|��EU���,K��NR��kQ������������$]����;�sX�'i^�~K+�HnG7���4+$���eE�I�i�ijd���h���D[���) F��r�M���U��Xl2�3�����4�����zv;
$�)��=�9D�7�hRBe�H����2j�s�h����|�P�f'j8������\��	��%c����-p��4U�3��-�o���{�nm�)����uA�<�PU�4�|�V�IU�~���g��/��xV�q=�sM�R��C����&�ZO���"�Gw7�]z7���w1��S}GL��oq���I��8���$
��;��)������#Xp_��*�-�����;�,][������@M�Fh�p)�k��Tn
&�{�5��6��^��I�>�BE��t�S�� ~�D�m:b���hOxk{���c�"�����v*��j�]�{	��sn�P�m��gLUS�`1������G8bsx�)�=����Z<��i��������|��{��=2�����'�W��v;��O=&�~�0�s�*e��&���O����Y	4(6�h��s�5zl��M\���g6�
4�S�@�/_M���\5�%&3j`����I��&�O�\�<��`���w�-�m��$�Itj�����V��!v��N��q
�(i��uM���F3� h(d]��#"�9��r�,�v��n7�5}����o��&Vu�Q��R����]�%�R��BE�����jGP�S���;?m������=��"AL���j\$6-g��
_���7���������
��+�����5�ad�<��7���P� ��yL�4�XrK��r�������:t�,�b��k���ph='S��T�U4i5PCK	`�d��F�5�X�k�
 �Ae+<[B�%K��'��	5�N(T�t����Be�-]�F�+T	cset����C5Mf�9��r���L�_�z)1�V��9z����D>P�~-��:�?�����2T�M4"63`z����[��9V����.y1����E�i����t7�T��t�;�	�@��r�������(4+�uJG����,�Y��;����qp�zt�?PZ.���J�j���U:m�+p����C��M�w��)^�#X��2�{S�Fd;8�_��`��0:���v���������(T���|���Z	���s��������'��� S��������T;��2h����5���<O� pAY�pM�/�h
�T������w�K'���eNy�s�p`�����zI%C�dq�]�a��R�"7�]���*<EV�^,A{�K�ju����{$2 4�(�)9�F�]�����~B�lz�� 
���i[���<�	;�b& .[����c�������#�92+�A�$B@F��8����D`��0�K�C6�q������V������39Q*�,��-��GI���V����������%J�����A��.5���#8~7��0�=DG��d[i	������6��e�#(V�k�M���|!u�m�p4��<�B��^�^��������t�io���0�h}�+��%z0ur���k{k��������C�N������]:�ts���Sr�E�%�p��)�w�*9��q��s������x�iNl�$�1C�L�Q�GN���*�f��i����xz�����fb��LN"���H`��q_!�`�h������J+���aW���>Q�3z]�a���s����D�	����<��K�mc�rB�qnz�3��Ar�MBW�v5R����T�i{�4J�<�����-����m�{+I�`z)���S	��QG�Q�lq��an+��Hz-B��	J	pKcnZ�:j��E�M�Rm\c�g�X)T����U���J(�
��<]
�H��C�"Z�c"��"�'������f��A�@[:���S]I�u�v�����eG���(�?�}8�vNu��-���k��`�O�������B��	m�3`1���R�=����b��q|B�������@	��1��7���U�*C`���)H��Q�T��k�C�P�|GjD�l�&�'�iT����\���l)i���h�z�����uN����-�Ot�Rg	v�E�D$��CNi!����OX�����vu�o4���>wq�CE�P@!�5�1��v]�����K�������'��0B�����'Q��"���"aP*����j.��\�`��5��q���F'1J,�����8��E�)�r�"��j�S�PUS)ix����e���]���3N|��
P}�_��s�������'h;1<��Gx�����Jx+c�~��&ey^=3&Y;z~\��u���5��HD3��73�
J�O��}G�<��gQ�}W+���;������0������#�]��������b{�k3�D@�w�u��/$
L�XO���%"*����J��1��h-,s��DG��������D�Z���)�>���-zb��Z�L�'m�^���s��+"e�%
MI�
�J^	V"���D��e	na���>��C�l�"=������!�,��m3��S�l�dUx�%���J��
��>�E`�C-X;:�����I��"'b���y"�j%�ai���5�v���n~r2��jL�qKA��;��)�.��r��6/x��h):����4��=��ofz��C�n�8�I�]��k�'.�z��M��\�Y{wu`��;	�L�����$k��%I��X�0����+��J�12�pv��>{����DI�1�����m����Z6��t����%�5I�Y�j�k��4M#L�lZ�kL�t&}�kH�w5^+/%�c��Vm�|����v������i�Kv,.h��X[g����s�_V9�{����'���s�O|�y��*c������F�W��'G}�B��b�)|�z^�������
��x��s����J����(�����
&��7�#�W�E�F(���kw���H��>�;��l$�0���a
���(�W`a�Z��CDw�Z%�3��+A�9	O���t�������t/:\�������;s;Q���������M��k���d{��lz�*[����\Jc=�?��+B4�Q+DR*���@��g��?W�I[��w9�z������bF��5�?z��^G��>��{�ybEP��l�����#*��6'�=9:���;��Q��)Kh�)����t��j�q1B�X�r��co������}�0�l���w��ju�9�X#������� @��"�w�th������'ik�$��`�4L6l�j�`���C:�2��V�s
�3~57w&�j~���p'>��
�}r�U�@�����;�����2����#^Sx�b�����BZ�.�C�b����+u5f����p}k�F����&a�
������
r���J4�i����$��8.|L����/�+~'�I3��1�	��XG�d.X����U'dB��)���J�g�
A��K�B���e�#�����Ae��9�/!��|?Wus�������e�q�k���L�QkR4)�F8��nF
�nd��2p�?���F����OF�������-���)���0C{�2���4��]��nOm$:�6��M�����a�p��C0��@��q_���DS���TB@��*��RI~���U�U��C$9W���Ut�����R|r_�<r/l'&@:�������\��`+����j�n�m^�qso�������E^�;�lD��
P�(�yO7bL���4}�qk��z��_t��*\p�M�\!|eyWp���A���a#*�����^�
���T��>���������k�[���p���7�~�f���f��=�w����gb�.[�&�00�c`1�:����q�����(������1��l1ASG����025���R��������r��~-yg��jid�����F�,B'��j9��s�����W�{b���*z�Sh��i��U
-n��L��s��3�~�����3�&����i�	�[q��mo�O�5O���n����fg�^h"P���cG������a���}�������h��=�Z���S���u���:��I����<<���3�\�v�FA[T/%$z����8����J�7��l6�� ��jm���$���!�4e��d����d��t��5�����E��yg��:�e���c�D�i�4�W�C��7O��k�<����wd)��B��P�����5���'�%W7��"5A�a���,VA��T�9'�u���������������Z��j��j��:��;�6��yljN{�S�"��:
��:�����Mi0��O?U�f��S�}���]��#����R�bb�.�L�d�jc ����[�����Z�T�G����%�����-N���N���Y{3���Y�,E���6QiU6)kQ��p�f}{ag[\��d�VU\R�7�����
�B����<�,@��{@v����
zA��m���=��F�I����5��t��c�O��>������^M�������/�H��G�9+~\[qiu^=��_��JR�!��g^&)4e�;�D%/*��;��"��.z,Y��"����C1�']O!/a�9$t�Y������D8��V�,���w�n�5'��	S��e:��*EC�F�"�!��a���KL������\�������j7D,p������~)��������	I"�oFa�
��c@������n�y����V��N�����k0���I�����/HS���W������������s���;�c11�������������Q{��C�t��Ki��\y��ca�����X�p�����*�\�]]X++�U����
8�T%]����
�)�,o���i������eR����9��U��^h?���V��0�W5.��(���3�H��'5�tN����S���[�B�v2^,r�&�WU8�iK��<Q^b(�o����4^,�I�������uT���xS7���L�_L��k'��N��W����xr�k�H����Dx�*��t+��]�����+���c�D��u��R��).��H0����c`���j��z��zy�W)�2!NU���L����'��0:"��g��>
��^F99��B~�i�c��=VFP�P���2�� �|�H�5�&/^�=M�Ik�~7	��C�e��I ���Uv&�F<����(6J*��?0���{aF�h�����k�G1���(E��>>pF� ����uu+���T>����%�e�*��:���-G���
 D	��*j`;g����:���w���sC,��3��j���6��0p0��	fr�e8)������LG'��#��v���,�\�r��rF��8�X}����G�������e:����~}��t{wc
S��K:�L���W*d��o��g���*�]��N���L�cYz��*�+s�R���h�-j'�6����@��r��0�f���b��SA��L$O*
��M������&z�.��i_���l���xo`��D+�������W�#���6q��c�~��Q�R��s�b(��}V�R��B�����Q{�����@����}NW�,8Cv�����b����Qg�,�K�	��a��6{���
���d6H��^����6���r,����eY���-���i�k�#�g�~2CHN��3��L��.�E�QP�r�ju4��fD5bI�����o���o��f����3|���*�r��l�(j�N�w���Z��
6��E��3��h�vR*��`s?���oln�7L���k� ��y��UgG!��Y���~�~����o&9(o^����=#K1I<��A������r�����q ��2cq��#t[�����������g&���`}"[L�� �x0���i�1�����9���<�����vA��nzqH1�k<4�[��QK[m��c�p�OV*
�b/�1���a,�&�����5�jcYF�-���T�9o�2��%f�x"~�6v_<��IM����zn�	����A��b���������.���������iX���M}2�q/�����d�Q���L��D��l��C���3�+�$& �w��Q����o�.chX�Q���tQ6A�Z�Q�m��ee�L����� �=�@pVN��H����`N��>E8����F+���u]�6R)�d6�{��Ul
���
�*3�U	��Z�{X��az�P����r�&O��;�f
�Rlj)������Z'��0��E������:UHP���^2�ki$�"�7�+#��s�R;	l���I�+�L���x��V��I8����m�[�/DI�j��3�Hs�����	�V����,(�������jU�L5�36�)kX��^N����w�Pj��������F�%6Q�c�ps�I��[NLI����@%~C������Mh�i��R���u��e�R�9kD
�q�I�Eduog�eI2�,���\V�&���SO
Mv�4Fd��a���q���������K�
�p����i�<}r�W�e��b/���h3�n�A������9i���\
h��+0K��<��
PO�:�(r(�w�!D�W�C~z6j�J�z�$��$����� `��{�R�P��Ug��(�1u���uD0��#�@A_���',�6�mKP�f��
�F�i�q1��'�����>n%�N���8+4c3�J(�6��D���w�y������U+1�[WPC4u�
[H��+�^������f���g] �p�[���Y�>g��7E_�t'l�?�������&�4��{;w*x�)y�D�!�;��5P�,�6�l�0?=�h0�D������D���)�|�h���[J��� ������N�Ms�o�(&('�o.N|��h��1���X����+�3Co��	g�.�=�t'L~0���=�(�s�L4��_����	{#>	3t)5����;Q���r����G3F��0Fz���"6���$�:�MM��<>r�����RY�J��~"GH/6��G�p�j
k�HtQ�;�}7i;-������g��<���T��B���c7�s��w{7�jK�sn�{W�X��,��G�fKrK����x��H�����������d��s��������W���W?�_~�7X�������udM������i�o��|�����zRF��b"�������W��2�Jpk�H�,���U��y2vC'	�l�<�v�������B��������`��P��@���q�\���=�.�GQfB����k�����tz-�I��RZI\b����J"����<����r��h%��\����cU%������J^���u�r0e��=�����:(d��H��%a�,g_����}L���4�3B&�Fqb�
���8���]%e�Vk8`�;O���1�V ���$��oU���$R���� ���2*T�����_C��5H4�;�����D����f��PH��*:I�5&U_����-�-4|D�T������x���<4Q��6����D���dM��w��1�$�S*C���r?�2JY�T���v0;��<����l��
������m���U������)E��H����vxV�;w7��HHBL\��M���~����ds���X$0�������|HM�z9V�����Z��!u�\���p	����{�N��)�JH+c�B�����eH��'���3�Q� >�����I�2'��wt����5#i�,<+�3��Cg���M��lV��D����>Fv�5&&<�W�N���^S��Y
�IJ�>�6����5�^Q�W8�R����Jvpv��T�F���v�2��(�p���)��v���4P���b�>�\��wh��@�86?iu.�IB~��Q��M\%V�.q��^�9Mmy�y�d
�x&���=dg8����
�/�pl�vs����,���s
��_�;T��/��\5�� �.�~3�u��I�	��x���fp������e��b�S�����|��'�|�d?{f��@\D�vp��
}�,~x�J/�JF�w6V�[�!�'ta��DpD=v���J��%��D�<��4��RXk�.}+-�O(u�2/��|�����<��No�US:3)������O�K����s�_�!FO~�+T+��2'p�W2��H�
���m�� �����b`�!��#�Iq��Q��8������o9}B�����_F)����,_���U/`xR��r��K`r��1E�*�4[�Hu�� �D��2b
I?�����J��)�&l�i�+����1xOO'���8�d�>e���+����Q�0��f�@1z������/��XF�wY�p{�Ip+
��&�h�e����bn���P
Z��C���o(��&�&v$�]�S�9�����2]��7�.�@��4�ztzj�i�M�D��R����b��R�)����)EET������#��4�CP�������m�O7��k�A&�`��E�86w�����L�I�_X���q��xx�����L%�5���B��@���m�9��+6�H0��)�J� ���u����ic.�X��Mt� <0{��]C	�c��
'�{#q�":ho-��(��/.�����(����h��[�e�%h��y�
v������*�Hw�9��t{�����~-7
���<\F����j��g���Gt�{��O��������`�T��9u���
�Q������T6��`~����m��c����6g)����=3����)�S�-C��e�2�}�w;�����������z�{���J��p_�%X���7C}������`�+�o�6*��n�x��;G��[y�������[{�s�`����Z���x=�t�[�~����	�@���Qp��R��v��
mp�Q�Sv����'Y��]/P:�3w�[y����r�es�4'�����&���MdL0Uo?g�|nUg��=w6�G���:9���|�6aag4������$T�s*�,1>�������s:���*�E\P�5�P���-��4)��i��/���c�P�/��<� ��1Ol����j�ZUrU����s�\u��R���i��Xx�yO�2��63 �p�GN���E���5��#/:7}*C{98����,���i�������z9�b�����5�
� �[�E�b�0�����/��)I�#-��S��t}�*L�q�a�R�K�v���������,�����} so:���������
�	P�7~�El�U�w�w]�
�����}�=�F���-t���s�Pe��7��s��p
���7�i����h���.�|c0{�
�z2�v���@
L��V�j���J����lS�Q�plZ��;yf�K�=��8b��Xk�����o�x��mg?|N�c����A��Rz:�q��Q�r��KI�"Ay����
`�&J�R##GTg�����J��W��yems�l�2�Jy�2��$��
�P�(��Je�2�G�oH��D1�����w315���>SD-t�[.����2^
m�=���wG>��GT6��j��7��o�,��p�J.f������S�P��?�N}8��5
��8j�J�j4|[�X���mF����6|�9*�uY�����U����������+���{X$����b��nL�1���W]���z�j{�3�ro]����E��2^��x���Dn�C�X��}�%y�4�Exm�y��8���E@{/;o�FE`�I�����P���}���r5��r�Z��u���~���;����@�[�/{GC3.T�O_����&��8�e����.w�
XZ���1��.h���y��y�s��M8K�eY�$S�0N�����	�����"�����#Y'����bk��0}3�l1����,�em����;49�PNN�l�9�X� ��
bnV�i���Jd���������"'SNd�df���
�r��\�u6.���;���
S�."�*1����|�'K�F�J�j`����\������*�
zNV�����t#l7oz�A��N	�i�*LICC���'4�
�8m�Y#����F�"T�5%���gv����aGh�L��x,�n7�W��	���6�e�Mup�>D2$:GD�>�3WVd�B|U_d���#�vrY���7F��G��mV����1}	��
������x���19���;���:	d]��g!��"f�
�E>}�T��)����N�@�Q/<FL�ur�� ��lJr�`��!�����kX)<U��|{����w��N~���l�v�>�8p�J�%�hM����H�&��P~x����;	C�d� �}p�.w���q����dH0%
g6���}\rpu�#�3)E� +m�Vp�)�}���D����*��/+�#I72sZ�q'��,���uA���8���2E��U'T�/s�ENfd���|��b*:����@��fD��s_sp3��DO9f��Nf������_:y%�6�O������p��|�4mx��R"f���>�!(��mz�L@��D��&v'^f\��F��]��q���7�5CJa���H��}/�� �
W�2^�B�iL�p�����Ywp���s�V"
���"��L���8.)C�
Nu���������HT@�L�����E�g��	��c�������e�����%���Q�p��U4�����>H��!�\�BFF�S[���r�]K�����?���,:����	&�
�>����d]A�~L�H�%�g��X�����9�w�
�Ye��)?��,�����$�+dt�9�unBoE�,>���7������A���X��S�N+`\t���Wh���z�!H���q�X�2&��dD�{��x&�M0�]o����s��r�9��x�CU+X���(��
1����'=Z��F����%���@�8���Co@+�*���E��	��!�7����<??[�#@��vW`m3��C.g��&����a(;m}�v(;n%i��9L9�e����m}��s�v�O:�A��Apx@�Q�"���(N���b_�Sn��y�v�����0���,�#@��9����W\Vl����L����d��,�����v�Lz�;S�
$0��P����E5�^����@�@�EFq#b!��
����7-�������K�u3����k����H5����G�m����>�1�W<dJ+�<0s�fM�370e�������
�,z�x0���9:�)���Gb%J���'*V�h��:K	c���\��*z�y8������;O"t�8h�N��2��(:��+��TL>N�9F������� +�Ca���YM9'��P���;��kOzGN�l�[�'B
�,]6�D�[�,�4�=��[�B�qW@��B��T7�y`G�#@��I���������sBK���Xu�3��)��N|I��%�4��<��Y�q6T����Ud�8�x����.���h���Q���q�����{R\1�nl�)*E�f��p�{�1�;[
&#d�{��4�A�N��tG��8�W�BF��@�����|���tt���GG^�5�>�V�~��g�p��=�{��~��5��"�>�l+\+m8��4��+zna���8qP~�m��N�M��rL.��f�`r��,�i+�6w�X���`�����rLk7|Q�lY�a}!c�X���M�azI������V�K���pC�9�j��A���Y����0Z�C*����[��]���@X��*�!C������C�D����zwz�-��t\T�4�z�x��HJ����xRE'<a��;�d���V�c�cQhO��SC�>�����;��!�����dX63��;���d)����!�T���
d(��N_q���\-��Cm�U��M��<F��v43^�r[	��Y���,���1(8���C�^

�����c��4����H*���H�8"N�J%��)dr�P(��Z��Y��������l��4x��� _�K����0xa3����r��������^�� �J�D��kd 6��V����3�'^���i������tB����G{���3�����o�������h�q+���1��~W����.��Q�b�q��"�"1������V���<{�HL��p�P��Q��)g�X/�,h�;�;:SJ�xr\o�lQ!��>|j�s��)�'�A~=�3�p�h��5�O��Pe���"qa�Bj����a�OP:D���������_���j��k��K�Z�hX�g�$�vE�����V�<�J�g��Jr�t������������#���QU}N��������/�����Xk~��?&�_&S�o��&V����Os�k�
�
I+��� �*@	�+O��t�,
��@~v�{��?y�������}�-�B�a�P3�������3��?�����l�^����������[I5uF�#���}��/-�f��67	���5\@&.c�Z4��_�zfP>����#�����'E�������5)�K3')���_!��|���
�����d��S(NT8f}���y~	�
bmky|TC�����w�yu6ii�:�B8�V�}��
Cem�d�S�������?�p�f�Jb
""����\T���������I"a�> A1�^LE��@�s��+��H� Yz.�7�C[�v�>�g��+t��=�����g���*���3'B�V'����>q�pSm�>#�W��j��/�/^ES�ut&�U�$W_�r�E�E����g�����@�<����ks�ec�
M��$��\�1� �o�0t�������\�����p�\����S�`��*_"��F+K&��N8��Q*���`�hI�;D.F��|�#��J_/|<ol�8o����
�!i�^1�
��H�����'��N�~HM3�n��r��%�fL�ij��D�k�y�LW�~�q������.3Ji��Ul^�&���B�*}���d5C�Ap�j��8e�'�}���;�[����'K�\t��a$�XS�<��`I��QhW�#p~��q����u����i���P��s����F9��r�4��*�OF������G�����+�&����k�-P~��d����N1������tN�j��Z�~����1K4�&8_���A�P���&(%p�s�jeF���4+9��R�.��HX
�=>������P�r�G��M�L��MI|W���P��$���;*y�e����)T��i|	"�<%�X�)Q�Y.W�����p�w�:�	��*�����RBQB~M�,]�<�~�Q�m����$i�x��x�d:mZ�)9�- ��E<GC�Te���"A�G�.��g�TaRij0����g^r��S�.J�dgh4��U"��Il���^�a����/\E�N�~���F����S���!g^k����]h�n�o�C_i�k/��VD��]2������2|m��`j�{!WB<�9�1��Xi���v-k�~zW�iZb9{�����Nh.n��+��=�>g�*�1G��?�<�>U����~n�� �w���6�$*t������N���"
��z��,Jo�0�7[QVoyO���<(Y���Q�wqS��
�"���}�-����OF'�5(�	q���L��Q6K#���+���P��~��Q@�o���%*�����������u�����	���`�����5��V�YS�����6'�z�s:U�;2�b6�B]p����r�����V��W�{������������j��1��A�ZZ*d��L�����:S�&����:^�'�~-����k�`y�����9�f�0��Bc�f��2/���b�$W��ag����E���lbnFB�>,��<�:�9�C(*��N�'��!���Q��P�8���#�My���3����@��'��W\U�3��!T���([�D$Y_[��a�P���$Z�V�'�.T�z�O=	~��Wx����u4#�+��"9�$���@$�����C�uoS&�`8�����r�� S��%�Iw�4Xs:�����ih�/.T�2�#���V}D[5N������i�
5�������"a�#�'q.PA�q�U�IU
gz�L��,����$�bp�{G�18:T������m�P��~��������%���~�0G��y8:��3���0����hX�"��x�����H�*#�
+J������A��0V)�
cLl�
��c���?���Nc�(��i��5��I�v2:���'{Liv����K*x�����v�8J�K=�fo�te������
����\=[�$3�/>-T����G���$f!j�E�p��;���{G��.�_k/BLO�(��s�k�:�#��Ya�l�m��m�b8���{����0!���<+�JFQ�0��D@Ucn�l���k�6:8k��P�a��
	MBY������(p��n�G6���>�[{����X4��o��������Z�����J�����X1��t�P�&�T���������.����k`��WWw��owE#{�K-��d�~@��q�E��I	����E��%��>
��1����J�C�<�����B��z�����.���>��}!�P����Zu��k�J�!T����:������������Y%(�F!SX�$�d�I��i�&��43�_��j�����C�!2����<���[��,2Q��C=�A��r�H�B��9�8I�����G���u�9iY8��,,�]+0��3&���I�7��	����x�v�L�Le:g�0[y�4�D�HoTb*6��4Z@����������v�!����=-S^��UN�q����ge%��o��C�?����a	�L'0��<L�2N������=���2M���B�"�������>��7������!��R�7��JU����Zvu��faZ8���XLu~�/���=C�d+g�C��X�d���<Z�U�@A4C!������%�������
A�e������~�`��~��e��m�&2i"v��@y]��#/�_X�����@�)��
�n�H3{�kQ��Z�5�xX�
������I���x�h2w(Zin������J��E�o�74�u�3������M���������a���e���*p^�����V�V�������:�5����4��+�k�=�_�p�I�*�_��H��>"hV����6����v�~�Z���Z��g�6��w��
�����''����-��[�v{���nq��w���67��S��F���s����w0���g7������	����r<86�@�A/�x�a��3U�/�Q+OC*%:�R�C�V�i�v��2��P���p2�vi�;��#�R"� /�%Nj4���^�O[�\s����t��dA��i@w� ��m�x�t�|�Bw�kBUg���dFJ��:u��,i��p�>���aYB��6�d�k��r�*o)�������*���~I�v�H�5C
��\d���l�ss<[O]��6�\�*�����upNI��.V��l/�d�P��Mw�2�j���~���A�E�9���I�����4\mM�t��,�f����aC�,Q�z�Zq��^c
��|s�vf���I���;DZ3
��MT��P��$
��Ut�FdN��
��F�`�w�WJ�ntG���Hxp
����1�,�W�����!0E�����/5�o�h,��nt��@�]0�������	GH��6#���`+��4Dd!��M��R�q(G]S�O%���A9��W�������������G*�V����,_4���fFh��J���s<�y��1�������
�=j2����eh���L��a)��P<e{�Q�Y��n�NOU
;H`��p�L6��#]�
�����}.�o�����#���#���Tyd�BQa��
��DR��r�@�T���.aj���N��({�J]�����J�{�|�+���ha��R�/a�'	A�B�,�F����F���Sq��	�a��3d�M������%�O8S.�
���E�7^#-���H�@.���:^K0�+
W���r�����<�Y�������n�"Ce`�y���ED��L� ��x&���w���L�Y��"Y�����W�u��+PK�A��0�b����X`����fY��V�iLg�7�;�M��a�,�-[�%��6�S���j\�]|�\��l�w�c�Ce����T��!��L���a���a��0XWD'�Y�M�H��)��V
R�S�6QnN�x{���S.	I{��"N_Yt�&P���)�WS 
��,B�	�v����c���j�10��)44G�g�CA�����c��e��,�x����/��I��n~]+� ��N�Kc"7l�q��hA!V�ef��u}��6&���rc�����R";Jv���W![&��Y��X����~	1DX��$��	�G��>2���)M�	d{�Z~����/��CQ�I5;6��<8����"�Rh�����^��2���>8K�/����!N{-�1	[�(2J��)Q�m)RC^�w����x�G
���u�7V���
�^q(��Jm���f@���t���4�����Gj�����{��] �_�J��qOCr�K)��� ;�<����B�f��S�M��qf�0	D8]�rCX1�bOD��ub��>
�r��BI$B����\M/����O���*�.�I�b��D/�b9�
�O�|f���(Q������]n��i�e�R��M�6+:%+���;��V�%���V2�B=0���8��6���H
|^}D5g�v��^ET���d��c�����9��8#��?@G���O�E3�{�������i2�C MT���7�8e����W�m���jV{b;�������Q���L�����;2v|E�o=2��L�7 %[%yY�J�i���� )���j����e�
$
�����Q����9��r8�U��������,;+,*��
����gJ��s]��3��%H$��\���`�:x	q]�-#���Y����,�K|*���ZGxxz$s�Ea8����$IaI�����*^��!���mN�l�������=kj����[zLH/n1]��QpZ��;�G���(�@D����nK�!U�M��[%���c���N������E��}X����	� (:�����#������"���44�����9�����Y{�L�H��]�0j����wpeP'����_@�P���������d��r�Q�zx-�����X/PH����y��#q������%�Z	����yP��1N�~�<�w�.��3�6f���L�^����@"v�<2-��nb�z�~���Z:b;9� y�A�3����G�&e �%<��*+�?�CFw�g��5�T�0����K�R8Y����X�Q�E;<#�Gm��c�#[��FA���E�J����Bs���uN�3����	��%�
�L�KK
k5��?�I(n1y������fM"JP�g�	��b�pD�~�I������S�E<�G�=�C2J0/z
�D��Y�%�o��:����t�tX�m����[���Mg�#x���{�G��WjL�o���v�yU-�E(��<�~1Q�<^]������Io�c�p���O����3?�x�X�_������\�|�%���#ZO������d�<�z���mH��XS(�L�;�l���,�z0����l����?w��KS�����O���}���V�_o�r��+����h�-	uJUZ�0�#��g0\]B�]��H�Nd�h��������5{V�'^��L`�����9�u/�{#�!m���I:	��%�h�jZ5�^O���m��O
3uZ���W~� �2���G
8�&3Zx��C�o����V��V�/99+9���h_�s�������5��4�n����9�#�_5W+�������$�����2 J":|����d���#�N>�UU"���sW<� *���R���ZOi�0�G������>H��E�ih�B�}��?�;�
ke6�b:_���l�Mf�I�rR(�mv��@��P4������J��J��C^�}�
�:UTurk��O��he�xV�`�����dYD|iw�dj���rq���Ma'�H�u����.�-�L�"��O��V�CE�<m�cw32�"�DtY��������?���w��w�_��K��~�R���LZ��uK������J>wruA��K��@�"�TKNL��_�9P�:�q+�����<2>�-(�4<�����H�uvR^��e}�r��5�����#TE�S��<����Yf9��7��rm)b�f�?{�t�SKwe����R���2��?��DA���	!&�������9����
J�K*���5���2����D�1u9���0
R��2V�f�.�����*�t}\fIz���>G������e��D06����t�l �� q����"N0xi�Ir����:^'�F�<MY���sf�G�@��R!yU�F1&��<�qR;]�M��Y�������j�2�sz
��&�� [S�"`�����;��{`���F��uD��������y��>��a
T��
��X��K��8�K���|V��$4�Q���GIHu�[c�"t��Dx��4z3t�����<��%��t(	��	%�����cI5��3g���
b�t�I�j&�
c��������H��`a�U��CCq�e[c="��]����Kb_�����f�V�leS�[;���r�����K`h�N-�b}�6�)	�R�)���U)H�g��|;�q��
�W$5�M��R�9�m�v_._3��o7�5�[��<�u�R���_U6r��*��O_m�o)�GV���s*�����?��;G��
�t^��^�
Vi�w����;| `jXNP�s���<�y�Coub��dt7��nj)��fK������t��2����}
@��c����p��D0��bL��o�����DUT��'�^����tFJ��e^�Z�,�`hMV���X�UX�������FG�.�tgr\Ay�gz�(n��+wl�T��

�-q-2�����o��7��CT����%�$0�W�J���oC�R�����}�
��������^���b*���T����,c/W���@jn�H�US
{N~��"@j��/@ob(�@��������}����*gT�@�xm�G��zT��?^V;�3)H��H�,�J��`��N���I������jV�-A\DZ}����v�
V)��s�a���
�����J�[��#��������
��SV@�/�W� �]:��h��KR�Gx��k�*�Q6��m���i�L�!}���b��M\Dn��"����Zpe��,02w����$}A��Z{��r|�|S�Usg��4��o�H���k^_Q
`��7���C)r��I��)CuO��N���G_��D��c�Y�rm�	�/�l�9�����*=����E���A�{]�(�L>�����z�*0��7�����lt%o�
��6	F���`���nL�Um^L6�IM��"_N�^���?lcV���i<�r�	��Q���L35���Jy&?
+��I��FJ�^�{�x�Nlx]�n��"�1�gsd�cS��B�>?�
����4x��@�|����v ��������;���i��t:����s��unwks?1�g�+�T���$��yJ���JM�U��,9�$a-Ih��Q�n��g%����do�U�k8�Ze`���R�Ww�j��h���#�b��v���z�Uo�f���`���	�:����O�����=9�����u�Ao��Bc��mHk�T��B/��|�����������N{g�k7A��';K�>���������G�>����?�K��d�����O��y���'O��{+�o���`�\�qZVn������<x� 0k>��W�[{�����7������g�������|���G��{���owwk��z�:���;��?����������������A����i��pe 
`+_��&����}��_/������b�Jc�
/��l������_jA&<~@N���
:�t^G���v����v��aX�\���������.��qsR��|t��:�=C5T�8���b;G �M�$
�d�:����_9'�y`�	3����RY.CU��]�$�*H>=��R���	��B�
����L3`���G8�W6�1�`�i����f�����O[{�'{f���o��7O�b�v-^��)�����C{��/c�
-���6��UF�"�If1�� ��Jk��o
��q�TN�?t-�������������).'����k��}������?�t,�nsgKd�$`+�h��F�\��V���"qc��/�����3=E��p<DB�@����"��xG���?X��m`"��,_y\�;	.�E���1���"��4�-6�>R�{�r`�L�j��d���� l�Pd�	�{�A�"�C@��BR��/�<�p�b9�'���>�������n���O��=��=������������9��[���[h���o��?z�]���{�;��������?�S�k^�b�o$�[^�{x��q��h=�/�
b���%�=����a���88��:��a��xmU����P���
F~?����:�Ut:^��r<��\x-��^���Ms�mi���N�1�-2�"oy�<�!�=�5�Q��#8l�v��mM�d�u<�T%����������� ��,M'�������yPk����?��[�p�n�Rv���������B�������I���W���������<���(8v��� m���������3�)�[���1m7k���wH������}�{�?>���o����5��Z�z�����%i�(@��h�5h��i<FA��6����\���}�������6�W�����!�6�]�`���uRo>{F���?7����E�������-���01?1{E����z���c��~U%]�t�_�G���0�((i�;&-,^��V;����r2o��E"���8o���5��!q�N�y+��:MV�L@��F�d�_F)�a)�r�Y!\�;&��xqF�Q��J/s���Wmk{{�N/(�:+j[��M�'y�j��Qk�u~��X��d6[Y�(w����=���:	8��l�C����n��������|�$y\R
��;>����8�����j�9MZ�i�h2�D���d:�O��9�a���4llRZ\������K�sA*�TW�A��@6����~��������!����z�����w�E]����v U������Z��6o(�I+���q���������<������:P�����p����sM����k�����|�ww��:��a�DTq�O��n���r�c�Cs�� wX������oB������m����^����IoK��)���b.��{��������I�t��C��t�8�/M��������~+t���Io��Jp�>��>���;cN�;�T�����|���z������%�s�����s�[���S���������]�Jr����.Q���%�@Z��
&N5�����
�rln�>&koH!�%��27m����z0m;m<|�y�����(���7����IR����z�w���s�aX�d��&��E���>�y�USx���ow��Kg���'����[��u�������V~���<:C���f���A��1�>y�5q,,�i��G_0��kx|(3?�,:�����^����ms����v��.�Y��B���m�����$�	z�����$0���j��F�����&|��QU�N/�<���T�5�u	���	��$�6M��h����
�]^�=3U2O���yz�9�������{�p�gi:'���v��
H�� qw~�e~����M=���� ���
��I�s�������w������s����}�>w������s����}�>w������s����}�>w����������c��(
#2Michael Paquier
michael.paquier@gmail.com
In reply to: Shigeru Hanada (#1)
Re: WIP: Join push-down for foreign tables

Hi,

I am interested in the development you are doing regarding join push down
and fdw stuff for remote postgreSQL servers.
Is there a way to get the postgres fdw you are providing here for common
9.1?
I saw that the tar you are providing here is adapted only for your patch.

Regards,

Michael

2011/9/14 Shigeru Hanada <shigeru.hanada@gmail.com>

Hi all,

I'd like to propose $SUBJECT for further foreign query optimization.
I've not finished development, but I'd appreciate it if I got someone's
review on my WIP code and its design.

Changes I made
==============

(1) Add foreign server OID to RelOptInfo
I think it would be nice to know whether a join comes from one foreign
server or not without digging into child nodes during considering paths
for a query. So I added serverid field to RelOptInfo, which defaults to
InvalidOid ,and is set to OID of the server if the node and all of its
children are from same foreign server. This also avoids looking catalog
up for foreign table entry to determine FDW routine.

(2) Add new planner node, ForeignJoinPath
ForeignJoinPath derives JoinPath, like other Join nodes, and holds
FdwPlan like ForeignPath node.

This node is used to represent a pushed-down join between foreign tables
and/or another foreign join in early planning phase, for all of
combination such as table-table, table-join, join-table and join-join
will be considered. In addition, though they might generate same
internal (FDW-specific) plan, reversed combination is considered;
planner generates two ForeignJoinPath for both (A & B) and (B & A).

During creation of this node, planner calls new FDW handler function
PlanForeignJoin to get a FdwPlan which includes costs and internal plan
of a foreign join. If a FDW can't (or doesn't want to) handle this
join, just return NULL is OK, and then planner gives such optimization
up and considers other usual join methods such as nested loop and hash
join.

A subtree which has a ForeignJoin on its top is translated into a
ForeignScan node during constructing a plan tree. This behavior is
different from other join path nodes such as NestPath and MergePath,
because they have child plan nodes correspond to path nodes.

(3) Add EXPALIN support for foreign join (currently just for debug)
ForeignScan might not be a simple foreign table scan, so
ExplainScanTarget() can't be used for it. An idea I have is adding
ExplainForeignScanTarget() to handle ForeignScan separately from other
scan nodes.

(4) Add new GUC parameter, enable_foreignjoin
If this was off, planner never generates ForeignJoinPath. In such case,
foreign tables will be joined with one of NestLoop, MergeJoin and HashJoin.

Known issue
===========

I'm sorry but attached patch, join_pushdown_v1.patch, is WIP, so
currently some kind of query fails. Known failure patterns are:

*) SELECT * FROM A JOIN B (...) doesn't work. Specifying columns in
SELECT clause explicitly like "SELECT A.col1, A.col2, ..." seems to work.
*) ORDER BY causes error if no column is specified in SELECT clause from
sort key's table.

Probably more problems still are there...

PG-wrapper as sample implementation
===================================

pgsql_fdw-0.1.0.tar.gz is an WIP implementation of PG-wrapper, which can
(hopefully) handle both simple foreign table scan and multiple foreign
joins. You can build it with placing in contrib/, or using pgxs. Note
that it has some issues such as memory leak of PGresult. I'm planning
to propose this wrapper as a contrib module, but it would be after
clearing such issues.

Regards,
--
Shigeru Hanada

--
Michael Paquier
http://michael.otacoo.com

#3Shigeru Hanada
shigeru.hanada@gmail.com
In reply to: Michael Paquier (#2)
Re: WIP: Join push-down for foreign tables

Hi Michael,

(2011/09/21 12:52), Michael Paquier wrote:

I am interested in the development you are doing regarding join push down
and fdw stuff for remote postgreSQL servers.
Is there a way to get the postgres fdw you are providing here for common
9.1?
I saw that the tar you are providing here is adapted only for your patch.

As you say, the pgsql_fdw I posted requires my join-push-down patch.
But, at least in current revision, using PG_VERSION_NUM would make it
compile-able for both 9.1 and 9.2. But I'm not sure that changes
required for 9.2 development cycle are enough small for this workaround.

Anyway, I'm going to publish recent pgsql_fdw for 9.1 on PGXN or
elsewhere, though it needs some (hopefully little) time.

Regards,
--
Shigeru Hanada

#4Michael Paquier
michael.paquier@gmail.com
In reply to: Shigeru Hanada (#3)
Re: WIP: Join push-down for foreign tables

2011/9/21 Shigeru Hanada <shigeru.hanada@gmail.com>

Hi Michael,

(2011/09/21 12:52), Michael Paquier wrote:

I am interested in the development you are doing regarding join push down
and fdw stuff for remote postgreSQL servers.
Is there a way to get the postgres fdw you are providing here for common
9.1?
I saw that the tar you are providing here is adapted only for your patch.

As you say, the pgsql_fdw I posted requires my join-push-down patch.
But, at least in current revision, using PG_VERSION_NUM would make it
compile-able for both 9.1 and 9.2. But I'm not sure that changes
required for 9.2 development cycle are enough small for this workaround.

OK, don't worry. I may be able to change that if necessary.

Anyway, I'm going to publish recent pgsql_fdw for 9.1 on PGXN or
elsewhere, though it needs some (hopefully little) time.

Thanks, that will be helpful, I think this work is very valuable for
postgresql and keep an eye on it. Btw, please don't feel that you have to
rush in doing that. I am not in a hurry at all so of course do it at your
rhythm.

Regards,
--
Michael Paquier
http://michael.otacoo.com

#5Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Shigeru Hanada (#1)
Re: WIP: Join push-down for foreign tables

Hanada-san,

I applied your patch and run a few test cases. while this test, I
noticed a few points.

At first, I tried to use file_fdw, however, it was crashed of course.
It seems to me this logic should be modified to confirm whether the target FDW
support join push down, or not.

+       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);
+       }
+

In my opinion, FdwRoutine should have an additional API to inform the core its
supported features; such as inner-join, outer-join, order-by,
group-by, aggregate
functions, insert, update, delete, etc... in the future version.

Obviously, it is not hard to implement inner/outer-join feature for
pgsql_fdw module,
but it may be a tough work for memcached_fdw module.

*) SELECT * FROM A JOIN B (...) doesn't work. Specifying columns in
SELECT clause explicitly like "SELECT A.col1, A.col2, ..." seems to work.
*) ORDER BY causes error if no column is specified in SELECT clause from
sort key's table.

I doubt these issues are in pgsql_fdw side, not the proposed patch itself.

In the case when the table and column names/types are compatible between
local-side and remote-side, the problem was not reproduced in my environment.
I'd like to suggest you to add a functionality to map remote column names to
the local ones in pgsql_fdw.

See below:

* I set up three foreign tables on the local side.
CREATE FOREIGN TABLE ft_1 (a int, b text) SERVER local_db;
CREATE FOREIGN TABLE ft_2 (x int, y text) SERVER local_db;
CREATE FOREIGN TABLE ft_3 (s int, t text) SERVER local_db;

* I also set up related tables on the remote side.
CREATE TABLE ft_1 (a int, b text);
CREATE TABLE ft_2 (x int, y text);
CREATE TABLE ft_3 (ss int, tt text);
Please note that column name of ft_3 is not compatible

* JOIN ft_1 and ft_2 works collectly.
postgres=# SELECT * FROM ft_1 JOIN ft_2 ON a = x;
a | b | x | y
---+-----+---+-----
2 | bbb | 2 | bbb
3 | ccc | 3 | ccc
4 | ddd | 4 | ddd
(3 rows)

postgres=# EXPLAIN SELECT * FROM ft_1 JOIN ft_2 ON a = x;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
Foreign Scan on multiple foreign tables (cost=0.00..0.00 rows=5000 width=72)
Remote SQL: SELECT ft_1.a, ft_1.b, ft_2.x, ft_2.y FROM public.ft_1
ft_1, public.ft_2 ft_2 WHERE (ft_1.a = ft_2.x)
(2 rows)

* JOIN ft_1 and ft_3 does not works. Error message says ft_3.s does
not exist. Probably, it means ft_3.s does not exist "on the remote
host".

postgres=# SELECT * FROM ft_1 JOIN ft_3 ON a = s;
ERROR: could not execute foreign query
DETAIL: ERROR: column ft_3.s does not exist
LINE 1: SELECT ft_1.a, ft_1.b, ft_3.s, ft_3.t FROM public.ft_1 ft_1,...
^

HINT: SELECT ft_1.a, ft_1.b, ft_3.s, ft_3.t FROM public.ft_1 ft_1,
public.ft_3 ft_3 WHERE (ft_1.a = ft_3.s)
postgres=# EXPLAIN SELECT * FROM ft_1 JOIN ft_3 ON a = s;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
Foreign Scan on multiple foreign tables (cost=0.00..0.00 rows=5000 width=72)
Remote SQL: SELECT ft_1.a, ft_1.b, ft_3.s, ft_3.t FROM public.ft_1
ft_1, public.ft_3 ft_3 WHERE (ft_1.a = ft_3.s)
(2 rows)

In fact, EXPLAIN shows us the remote SQL tries to reference ft_3.s,
instead of ft_3.ss.

Thanks,

2011年9月14日10:24 Shigeru Hanada <shigeru.hanada@gmail.com>:

Hi all,

I'd like to propose $SUBJECT for further foreign query optimization.
I've not finished development, but I'd appreciate it if I got someone's
review on my WIP code and its design.

Changes I made
==============

(1) Add foreign server OID to RelOptInfo
I think it would be nice to know whether a join comes from one foreign
server or not without digging into child nodes during considering paths
for a query. So I added serverid field to RelOptInfo, which defaults to
InvalidOid ,and is set to OID of the server if the node and all of its
children are from same foreign server. This also avoids looking catalog
up for foreign table entry to determine FDW routine.

(2) Add new planner node, ForeignJoinPath
ForeignJoinPath derives JoinPath, like other Join nodes, and holds
FdwPlan like ForeignPath node.

This node is used to represent a pushed-down join between foreign tables
and/or another foreign join in early planning phase, for all of
combination such as table-table, table-join, join-table and join-join
will be considered. In addition, though they might generate same
internal (FDW-specific) plan, reversed combination is considered;
planner generates two ForeignJoinPath for both (A & B) and (B & A).

During creation of this node, planner calls new FDW handler function
PlanForeignJoin to get a FdwPlan which includes costs and internal plan
of a foreign join. If a FDW can't (or doesn't want to) handle this
join, just return NULL is OK, and then planner gives such optimization
up and considers other usual join methods such as nested loop and hash join.

A subtree which has a ForeignJoin on its top is translated into a
ForeignScan node during constructing a plan tree. This behavior is
different from other join path nodes such as NestPath and MergePath,
because they have child plan nodes correspond to path nodes.

(3) Add EXPALIN support for foreign join (currently just for debug)
ForeignScan might not be a simple foreign table scan, so
ExplainScanTarget() can't be used for it. An idea I have is adding
ExplainForeignScanTarget() to handle ForeignScan separately from other
scan nodes.

(4) Add new GUC parameter, enable_foreignjoin
If this was off, planner never generates ForeignJoinPath. In such case,
foreign tables will be joined with one of NestLoop, MergeJoin and HashJoin.

Known issue
===========

I'm sorry but attached patch, join_pushdown_v1.patch, is WIP, so
currently some kind of query fails. Known failure patterns are:

*) SELECT * FROM A JOIN B (...) doesn't work. Specifying columns in
SELECT clause explicitly like "SELECT A.col1, A.col2, ..." seems to work.
*) ORDER BY causes error if no column is specified in SELECT clause from
sort key's table.

Probably more problems still are there...

PG-wrapper as sample implementation
===================================

pgsql_fdw-0.1.0.tar.gz is an WIP implementation of PG-wrapper, which can
(hopefully) handle both simple foreign table scan and multiple foreign
joins. You can build it with placing in contrib/, or using pgxs. Note
that it has some issues such as memory leak of PGresult. I'm planning
to propose this wrapper as a contrib module, but it would be after
clearing such issues.

Regards,
--
Shigeru Hanada

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#6Shigeru Hanada
shigeru.hanada@gmail.com
In reply to: Kohei KaiGai (#5)
Re: WIP: Join push-down for foreign tables

Kaigai-san,

Thanks for the review.

(2011/10/03 17:07), Kohei KaiGai wrote:

At first, I tried to use file_fdw, however, it was crashed of course.
It seems to me this logic should be modified to confirm whether the target FDW
support join push down, or not.

+       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);
+       }
+

In my opinion, FdwRoutine should have an additional API to inform the core its
supported features; such as inner-join, outer-join, order-by,
group-by, aggregate
functions, insert, update, delete, etc... in the future version.

Sure, so in my design PlanForeignJoin is optional.

The lack of capability is informed from FDW with setting function
pointer in FdwRoutine to NULL. If PlanForeignJoin was NULL, core
(planner) will give up to consider join push-down, and use one of local
join methods such as NestLoop and MergeJoin for those foreign tables.
As you say, other push-down-able features would also have optional
handler function for each.

BTW, what is the point of separating inner-join and outer-join in
context of push-down? I think that providing the type of the join to
FDW as parameter of PlanForeignJoin is enough. Then they can tell core
planner to give up considering join push-down by returning NULL if the
type of the join was not supported.

Obviously, it is not hard to implement inner/outer-join feature for
pgsql_fdw module,
but it may be a tough work for memcached_fdw module.

Agreed, join push-down might be useful for only RDBMS wrappers. NoSQL
wrapper would not provide handler functions other than required ones for
simple scan.

*) SELECT * FROM A JOIN B (...) doesn't work. Specifying columns in
SELECT clause explicitly like "SELECT A.col1, A.col2, ..." seems to work.
*) ORDER BY causes error if no column is specified in SELECT clause from
sort key's table.

I doubt these issues are in pgsql_fdw side, not the proposed patch itself.

Yes, this problem is from pgsql_fdw's SQL generator. I'll fix it.

In the case when the table and column names/types are compatible between
local-side and remote-side, the problem was not reproduced in my environment.
I'd like to suggest you to add a functionality to map remote column names to
the local ones in pgsql_fdw.

Since per-column FDW options patch has been committed in last CF, It's
not hard to implement colname FDW option for pgsql_fdw, and rough
implementation has been already done. In next post you will be able to
map column names. :)

Regards,
--
Shigeru Hanada

#7Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Shigeru Hanada (#6)
Re: WIP: Join push-down for foreign tables

2011年10月4日12:08 Shigeru Hanada <shigeru.hanada@gmail.com>:

In my opinion, FdwRoutine should have an additional API to inform the core its
supported features; such as inner-join, outer-join, order-by,
group-by, aggregate
functions, insert, update, delete, etc... in the future version.

Sure, so in my design PlanForeignJoin is optional.

The lack of capability is informed from FDW with setting function
pointer in FdwRoutine to NULL. If PlanForeignJoin was NULL, core
(planner) will give up to consider join push-down, and use one of local
join methods such as NestLoop and MergeJoin for those foreign tables.
As you say, other push-down-able features would also have optional
handler function for each.

Sorry, I overlooked it was already implemented at create_foreignjoin_path().

I additionally tried several cases using pgsql_fdw.
In some cases, it seems to me the planner don't push down the join tree
as you probably expected.
Please see the following example:

I defined three foreign tables: ft1(a int, b text), ft2(x int, y
text), ft3(s int, t text),
and lt1, lt2, lt3 are regular local tables.

postgres=# explain SELECT * FROM (ft1 join ft2 on a = x join ft3 on a = s);

QUERY PLAN
---------------------------------------------------------------------------------------------------------------
Foreign Scan on multiple foreign tables (cost=0.00..0.00 rows=25000 width=108)
Remote SQL: SELECT ft1.a, ft1.b, ft2.x, ft2.y, ft3.s, ft3.t FROM
public.ft1 ft1, public.ft2 ft2, public.ft3 ft3 WHERE (ft1.a = ft3.s)
AND (ft1.a = ft2.x)
(2 rows)

It works good.
(P.S. I noticed that pgsql_fdw has incorrect Assert(). Please fix
pgsql_fdw.c:730)

However, an existence of local relation makes planner confused.
It seems to me you expect "ft1 join ft2 on a = x"

postgres=# explain SELECT * FROM (ft1 join ft2 on a = x join lt3 on a = s);
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
Merge Join (cost=205.08..758.83 rows=30750 width=108)
Merge Cond: (ft1.a = lt3.s)
-> Merge Join (cost=119.66..199.66 rows=5000 width=72)
Merge Cond: (ft1.a = ft2.x)
-> Sort (cost=59.83..62.33 rows=1000 width=36)
Sort Key: ft1.a
-> Foreign Scan on ft1 (cost=10.00..10.00 rows=1000 width=36)
Remote SQL: DECLARE pgsql_fdw_cursor_2 SCROLL
CURSOR FOR SELECT ft1.a, ft1.b FROM public.ft1 ft1
-> Sort (cost=59.83..62.33 rows=1000 width=36)
Sort Key: ft2.x
-> Foreign Scan on ft2 (cost=10.00..10.00 rows=1000 width=36)
Remote SQL: DECLARE pgsql_fdw_cursor_3 SCROLL
CURSOR FOR SELECT ft2.x, ft2.y FROM public.ft2 ft2
-> Sort (cost=85.43..88.50 rows=1230 width=36)
Sort Key: lt3.s
-> Seq Scan on lt3 (cost=0.00..22.30 rows=1230 width=36)
(15 rows)

What is the reason why the foreign join is not pushed down?
Maybe, injected Sort plan prevent the planner to consider both side of
relations being foreign scan owned by same server? I'm still
investigating the reason.

I hope comments from committers. :-(

A collateral evidence is below.
If we try to sort the result by a key being not used to join, the both
of foreign scan gets pushed down.

postgres=# explain SELECT * FROM (ft1 join ft2 on a = x) order by y;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
Sort (cost=307.19..319.69 rows=5000 width=72)
Sort Key: ft2.y
-> Foreign Scan on multiple foreign tables (cost=0.00..0.00
rows=5000 width=72)
Remote SQL: SELECT ft1.a, ft1.b, ft2.x, ft2.y FROM public.ft1
ft1, public.ft2 ft2 WHERE (ft1.a = ft2.x)
(4 rows)

However, when I tried to sort by a key being used to join, the both of
foreign scan was not pushed down.

postgres=# explain SELECT * FROM (ft1 join ft2 on a = x) order by a;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------
Merge Join (cost=119.66..199.66 rows=5000 width=72)
Merge Cond: (ft1.a = ft2.x)
-> Sort (cost=59.83..62.33 rows=1000 width=36)
Sort Key: ft1.a
-> Foreign Scan on ft1 (cost=10.00..10.00 rows=1000 width=36)
Remote SQL: DECLARE pgsql_fdw_cursor_6 SCROLL CURSOR
FOR SELECT ft1.a, ft1.b FROM public.ft1 ft1
-> Sort (cost=59.83..62.33 rows=1000 width=36)
Sort Key: ft2.x
-> Foreign Scan on ft2 (cost=10.00..10.00 rows=1000 width=36)
Remote SQL: DECLARE pgsql_fdw_cursor_7 SCROLL CURSOR
FOR SELECT ft2.x, ft2.y FROM public.ft2 ft2
(10 rows)

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#8Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Kohei KaiGai (#7)
Re: WIP: Join push-down for foreign tables

Hanada-san,

The proposed patch put an invocation of PlanForeignJoin on the
create_foreignjoin_path() being also called by match_unsorted_outer().
Is it a suitable position to make a decision whether a join can be
pushed-down?

I think; it needs an additional functionality to provide higher priority
on the foreign-join plan that other plans, when fdw determind a particular
join can be pushed-down.
(Sorry, I have no idea right now.)

Let's see the following result.

postgres=# EXPLAIN SELECT * FROM ft1 , ft2, lt3 WHERE ft1.a = ft2.x
AND ft1.a = lt3.s;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
Merge Join (cost=205.08..758.83 rows=30750 width=108)
Merge Cond: (ft1.a = lt3.s)
-> Merge Join (cost=119.66..199.66 rows=5000 width=72)
Merge Cond: (ft1.a = ft2.x)
-> Sort (cost=59.83..62.33 rows=1000 width=36)
Sort Key: ft1.a
-> Foreign Scan on ft1 (cost=10.00..10.00 rows=1000 width=36)
Remote SQL: DECLARE pgsql_fdw_cursor_0 SCROLL
CURSOR FOR SELECT ft1.a, ft1.b FROM public.ft1 ft1
-> Sort (cost=59.83..62.33 rows=1000 width=36)
Sort Key: ft2.x
-> Foreign Scan on ft2 (cost=10.00..10.00 rows=1000 width=36)
Remote SQL: DECLARE pgsql_fdw_cursor_1 SCROLL
CURSOR FOR SELECT ft2.x, ft2.y FROM public.ft2 ft2
-> Sort (cost=85.43..88.50 rows=1230 width=36)
Sort Key: lt3.s
-> Seq Scan on lt3 (cost=0.00..22.30 rows=1230 width=36)
(15 rows)

Then, I turned off the enable_mergejoin.

postgres=# EXPLAIN SELECT * FROM ft1 , ft2, lt3 WHERE ft1.a = ft2.x
AND ft1.a = lt3.s;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------
Hash Join (cost=37.67..1126.42 rows=30750 width=108)
Hash Cond: (ft1.a = lt3.s)
-> Foreign Scan on multiple foreign tables (cost=0.00..0.00
rows=5000 width=72)
Remote SQL: SELECT ft1.a, ft1.b, ft2.x, ft2.y FROM public.ft1
ft1, public.ft2 ft2 WHERE (ft1.a = ft2.x)
-> Hash (cost=22.30..22.30 rows=1230 width=36)
-> Seq Scan on lt3 (cost=0.00..22.30 rows=1230 width=36)
(6 rows)

Probably, the basic design is correct. However, the planner gives
higher priority on the join plan between
local and foreign than pushing-down foreign relations.

Does it make sense not to consider any other possible plans when FDW
decided a particular join can be
pushed down?

Thanks,

2011年10月7日18:06 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2011年10月4日12:08 Shigeru Hanada <shigeru.hanada@gmail.com>:

In my opinion, FdwRoutine should have an additional API to inform the core its
supported features; such as inner-join, outer-join, order-by,
group-by, aggregate
functions, insert, update, delete, etc... in the future version.

Sure, so in my design PlanForeignJoin is optional.

The lack of capability is informed from FDW with setting function
pointer in FdwRoutine to NULL. If PlanForeignJoin was NULL, core
(planner) will give up to consider join push-down, and use one of local
join methods such as NestLoop and MergeJoin for those foreign tables.
As you say, other push-down-able features would also have optional
handler function for each.

Sorry, I overlooked it was already implemented at create_foreignjoin_path().

I additionally tried several cases using pgsql_fdw.
In some cases, it seems to me the planner don't push down the join tree
as you probably expected.
Please see the following example:

I defined three foreign tables: ft1(a int, b text), ft2(x int, y
text), ft3(s int, t text),
and lt1, lt2, lt3 are regular local tables.

postgres=# explain SELECT * FROM (ft1 join ft2 on a = x join ft3 on a = s);

QUERY PLAN
---------------------------------------------------------------------------------------------------------------
Foreign Scan on multiple foreign tables (cost=0.00..0.00 rows=25000 width=108)
Remote SQL: SELECT ft1.a, ft1.b, ft2.x, ft2.y, ft3.s, ft3.t FROM
public.ft1 ft1, public.ft2 ft2, public.ft3 ft3 WHERE (ft1.a = ft3.s)
AND (ft1.a = ft2.x)
(2 rows)

It works good.
(P.S. I noticed that pgsql_fdw has incorrect Assert(). Please fix
pgsql_fdw.c:730)

However, an existence of local relation makes planner confused.
It seems to me you expect "ft1 join ft2 on a = x"

postgres=# explain SELECT * FROM (ft1 join ft2 on a = x join lt3 on a = s);
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
Merge Join (cost=205.08..758.83 rows=30750 width=108)
Merge Cond: (ft1.a = lt3.s)
-> Merge Join (cost=119.66..199.66 rows=5000 width=72)
Merge Cond: (ft1.a = ft2.x)
-> Sort (cost=59.83..62.33 rows=1000 width=36)
Sort Key: ft1.a
-> Foreign Scan on ft1 (cost=10.00..10.00 rows=1000 width=36)
Remote SQL: DECLARE pgsql_fdw_cursor_2 SCROLL
CURSOR FOR SELECT ft1.a, ft1.b FROM public.ft1 ft1
-> Sort (cost=59.83..62.33 rows=1000 width=36)
Sort Key: ft2.x
-> Foreign Scan on ft2 (cost=10.00..10.00 rows=1000 width=36)
Remote SQL: DECLARE pgsql_fdw_cursor_3 SCROLL
CURSOR FOR SELECT ft2.x, ft2.y FROM public.ft2 ft2
-> Sort (cost=85.43..88.50 rows=1230 width=36)
Sort Key: lt3.s
-> Seq Scan on lt3 (cost=0.00..22.30 rows=1230 width=36)
(15 rows)

What is the reason why the foreign join is not pushed down?
Maybe, injected Sort plan prevent the planner to consider both side of
relations being foreign scan owned by same server? I'm still
investigating the reason.

I hope comments from committers. :-(

A collateral evidence is below.
If we try to sort the result by a key being not used to join, the both
of foreign scan gets pushed down.

postgres=# explain SELECT * FROM (ft1 join ft2 on a = x) order by y;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
Sort (cost=307.19..319.69 rows=5000 width=72)
Sort Key: ft2.y
-> Foreign Scan on multiple foreign tables (cost=0.00..0.00
rows=5000 width=72)
Remote SQL: SELECT ft1.a, ft1.b, ft2.x, ft2.y FROM public.ft1
ft1, public.ft2 ft2 WHERE (ft1.a = ft2.x)
(4 rows)

However, when I tried to sort by a key being used to join, the both of
foreign scan was not pushed down.

postgres=# explain SELECT * FROM (ft1 join ft2 on a = x) order by a;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------
Merge Join (cost=119.66..199.66 rows=5000 width=72)
Merge Cond: (ft1.a = ft2.x)
-> Sort (cost=59.83..62.33 rows=1000 width=36)
Sort Key: ft1.a
-> Foreign Scan on ft1 (cost=10.00..10.00 rows=1000 width=36)
Remote SQL: DECLARE pgsql_fdw_cursor_6 SCROLL CURSOR
FOR SELECT ft1.a, ft1.b FROM public.ft1 ft1
-> Sort (cost=59.83..62.33 rows=1000 width=36)
Sort Key: ft2.x
-> Foreign Scan on ft2 (cost=10.00..10.00 rows=1000 width=36)
Remote SQL: DECLARE pgsql_fdw_cursor_7 SCROLL CURSOR
FOR SELECT ft2.x, ft2.y FROM public.ft2 ft2
(10 rows)

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#9Florian Pflug
fgp@phlo.org
In reply to: Kohei KaiGai (#8)
Re: WIP: Join push-down for foreign tables

On Oct9, 2011, at 13:35 , Kohei KaiGai wrote:

I think; it needs an additional functionality to provide higher priority
on the foreign-join plan that other plans, when fdw determind a particular
join can be pushed-down.
(Sorry, I have no idea right now.)

Probably, the basic design is correct. However, the planner gives
higher priority on the join plan between
local and foreign than pushing-down foreign relations.

The textbook approach to that is to factor the cost of transferring the
rows over the network into the plan costs. That, of course, only works
once we have statistics for the foreign tables. But AFAIK we eventually
want to have those, so I'd say punt this until that time.

Does it make sense not to consider any other possible plans when FDW
decided a particular join can be
pushed down?

I think in the long run we're going to want a cost-based decision there.
Pushing down a join is only a win if the join selectivity is low. For a
selectivity close to 1.0, it may very well be many times more efficient
to fetch the tables separately and join them locally. You'll be fetching
only |A| + |B| rows for a local join, instead of |A| * |B| rows for a remote
join (assuming A,B are tables with cardinality |A|, |B|).

best regards,
Florian Pflug

#10Shigeru Hanada
shigeru.hanada@gmail.com
In reply to: Kohei KaiGai (#7)
Re: WIP: Join push-down for foreign tables

(2011/10/08 1:06), Kohei KaiGai wrote:

What is the reason why the foreign join is not pushed down?
Maybe, injected Sort plan prevent the planner to consider both side of
relations being foreign scan owned by same server? I'm still
investigating the reason.

Thanks for your testing.

I'm not sure, but I think that Sort plan node would not be the reason
because it's an element of merge join. Maybe some wrong points would be
in my join method consideration.

In my assumption, ft1 and ft2 should be joined first (because such join
has very low costs) and then that result and lt3 should be joined with
one of local join methods, such as merge join and hash join.

I'll try your example soon.

Regards,
--
Shigeru Hanada

#11Robert Haas
robertmhaas@gmail.com
In reply to: Shigeru Hanada (#10)
Re: WIP: Join push-down for foreign tables

2011/10/10 Shigeru Hanada <shigeru.hanada@gmail.com>:

(2011/10/08 1:06), Kohei KaiGai wrote:

What is the reason why the foreign join is not pushed down?
Maybe, injected Sort plan prevent the planner to consider both side of
relations being foreign scan owned by same server? I'm still
investigating the reason.

Thanks for your testing.

I'm not sure, but I think that Sort plan node would not be the reason
because it's an element of merge join.  Maybe some wrong points would be
in my join method consideration.

In my assumption, ft1 and ft2 should be joined first (because such join
has very low costs) and then that result and lt3 should be joined with
one of local join methods, such as merge join and hash join.

This might be out of left field, but wouldn't it make more sense to
get postgresql_fdw committed first, and then add the join push-down
functionality afterwards? I mean, otherwise, we're going to be left
with a situation where we have join pushdown in core, but the only FDW
that can actually make use of it elsewhere.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#11)
Re: WIP: Join push-down for foreign tables

Robert Haas <robertmhaas@gmail.com> writes:

This might be out of left field, but wouldn't it make more sense to
get postgresql_fdw committed first, and then add the join push-down
functionality afterwards? I mean, otherwise, we're going to be left
with a situation where we have join pushdown in core, but the only FDW
that can actually make use of it elsewhere.

There's likely to be a lot of FDW infrastructure that will not be
exercised by anything in core or contrib ...

regards, tom lane

#13Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Tom Lane (#12)
Re: WIP: Join push-down for foreign tables

How about the current status of this patch, although it is still
"Waiting on author".

If Hanada-san would propose contrib/pgsql_fdw as a basis of join-pushdown
feature, I'll likely volunteer to review the patch.
I'm also interested in this feature. Hopefully, I'd like to try other
kind of pushing
down (such as aggregate, sort, ...) or updatable foreign tables. :-)

Thanks,

2011/10/10 Tom Lane <tgl@sss.pgh.pa.us>:

Robert Haas <robertmhaas@gmail.com> writes:

This might be out of left field, but wouldn't it make more sense to
get postgresql_fdw committed first, and then add the join push-down
functionality afterwards?  I mean, otherwise, we're going to be left
with a situation where we have join pushdown in core, but the only FDW
that can actually make use of it elsewhere.

There's likely to be a lot of FDW infrastructure that will not be
exercised by anything in core or contrib ...

                       regards, tom lane

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#14Shigeru Hanada
shigeru.hanada@gmail.com
In reply to: Kohei KaiGai (#13)
3 attachment(s)
Re: WIP: Join push-down for foreign tables

(2011/10/21 23:53), Kohei KaiGai wrote:

How about the current status of this patch, although it is still
"Waiting on author".

I've found some issues for this CF item, and I would need some more
effort to solve them. So I'll mark this item as "Returned with
feedback", and I'll propose this idea again for next CF (2011-11). Then
I'll work with some other items in current CF (2011-09), which are
enough easy for me to work on.

If Hanada-san would propose contrib/pgsql_fdw as a basis of join-pushdown
feature, I'll likely volunteer to review the patch.
I'm also interested in this feature. Hopefully, I'd like to try other
kind of pushing
down (such as aggregate, sort, ...) or updatable foreign tables. :-)

Thanks for the offer. I'd like to propose pgsql_fdw as a contrib module
for 9.2. I've attached tree patches of revised version of pgsql_fdw
which doesn't have join-push-down capability, but IMO it would be enough
for basis of various push-down enhancement. Note that I chose the name
"pgsql_fdw" to avoid naming conflict with existing validator
postgresql_fdw_validator, which has been in core since 8.4 and used by
contrib/dblink.

It might be useful and reasonable to integrate pgsql_fdw_validator into
postgresql_fdw_validator and use it as a common validator for
contrib/dblink and new FDW postgresql_fdw, but I didn't do so (at least
not yet) because it would confuse users of contrib/dblink by accepting
non-libpq options.

- fdw_helper_doc.patch
This patch adds new document section describing about FDW helper
functions into "Chapter 50. Writing A Foreign Data Wrapper". This can
be back-patched to 9.1 because this patch contains only functions which
were introduced at 9.1 release.

- fdw_helper_funcs.patch (requires above patch has been applied)
This patch contains two new FDW helper functions.
GetForeignColumnOptions(), which retrieves all FDW options of a column
of a foreign table. This would be useful not only for pgsql_fdw but
also any FDW which have per-column FDW options.
This patch also adds GetFdwOptionValue(), which returns finest-grained
FDW option value for an option set on given foreign table or its column.
This would be useful for FDWs which accept an option on multiple object
level, e.g. such as foreign table and foreign server; this kind of usage
was mentioned by David Fetter in this message.
http://archives.postgresql.org/pgsql-hackers/2011-10/msg00483.php

- pgsql_fdw.patch (requires above patches have been applied)
This patch provides FDW for external PostgreSQL servers. Note that this
FDW uses pgsql_fdw_validator for its validator, not a built-in
postgresql_fdw_validator. Please see SGML document for details of this FDW.

Regards,
--
Shigeru Hanada

Attachments:

fdw_helper_funcs.patchtext/plain; name=fdw_helper_funcs.patchDownload
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 1cf3b3c..4a7ffa6 100644
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
***************
*** 26,32 ****
  #include "nodes/makefuncs.h"
  #include "optimizer/cost.h"
  #include "utils/rel.h"
! #include "utils/syscache.h"
  
  PG_MODULE_MAGIC;
  
--- 26,32 ----
  #include "nodes/makefuncs.h"
  #include "optimizer/cost.h"
  #include "utils/rel.h"
! #include "utils/lsyscache.h"
  
  PG_MODULE_MAGIC;
  
*************** get_file_fdw_attribute_options(Oid relid
*** 345,398 ****
  	/* Retrieve FDW options for all user-defined attributes. */
  	for (attnum = 1; attnum <= natts; attnum++)
  	{
! 		HeapTuple	tuple;
! 		Form_pg_attribute attr;
! 		Datum		datum;
! 		bool		isnull;
  
  		/* Skip dropped attributes. */
  		if (tupleDesc->attrs[attnum - 1]->attisdropped)
  			continue;
  
! 		/*
! 		 * We need the whole pg_attribute tuple not just what is in the
! 		 * tupleDesc, so must do a catalog lookup.
! 		 */
! 		tuple = SearchSysCache2(ATTNUM,
! 								RelationGetRelid(rel),
! 								Int16GetDatum(attnum));
! 		if (!HeapTupleIsValid(tuple))
! 			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
! 				 attnum, RelationGetRelid(rel));
! 		attr = (Form_pg_attribute) GETSTRUCT(tuple);
! 
! 		datum = SysCacheGetAttr(ATTNUM,
! 								tuple,
! 								Anum_pg_attribute_attfdwoptions,
! 								&isnull);
! 		if (!isnull)
  		{
! 			List	   *options = untransformRelOptions(datum);
! 			ListCell   *lc;
  
! 			foreach(lc, options)
  			{
! 				DefElem	   *def = (DefElem *) lfirst(lc);
! 
! 				if (strcmp(def->defname, "force_not_null") == 0)
  				{
! 					if (defGetBoolean(def))
! 					{
! 						char   *attname = pstrdup(NameStr(attr->attname));
  
! 						fnncolumns = lappend(fnncolumns, makeString(attname));
! 					}
  				}
- 				/* maybe in future handle other options here */
  			}
  		}
- 
- 		ReleaseSysCache(tuple);
  	}
  
  	heap_close(rel, AccessShareLock);
--- 345,373 ----
  	/* Retrieve FDW options for all user-defined attributes. */
  	for (attnum = 1; attnum <= natts; attnum++)
  	{
! 		List	   *options;
! 		ListCell   *lc;
  
  		/* Skip dropped attributes. */
  		if (tupleDesc->attrs[attnum - 1]->attisdropped)
  			continue;
  
! 		options = GetForeignColumnOptions(relid, attnum);
! 		foreach(lc, options)
  		{
! 			DefElem	   *def = (DefElem *) lfirst(lc);
  
! 			if (strcmp(def->defname, "force_not_null") == 0)
  			{
! 				if (defGetBoolean(def))
  				{
! 					char   *attname = pstrdup(get_attname(relid, attnum));
  
! 					fnncolumns = lappend(fnncolumns, makeString(attname));
  				}
  			}
+ 			/* maybe in future handle other options here */
  		}
  	}
  
  	heap_close(rel, AccessShareLock);
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index db02d13..43ad6ed 100644
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
*************** GetForeignTable(Oid relid);
*** 297,302 ****
--- 297,332 ----
      </para>
  
      <para>
+ <programlisting>
+ char *
+ GetFdwOptionValue(Oid relid, AttrNumber attnum, const char *optname);
+ </programlisting>
+ 
+      This function returns a copy (created in current memory context) of the
+      value of the given option for the given object (relation or its column).
+      If attnum is InvalidAttrNumber, pg_attribute is ignored.
+      If specified option is set in multiple object level, the one in the
+      finest-grained object is used; e.g. priority is given to user mapping 
+      over than a foreign server for the mapping or foreign-data wrapper for the
+      server.
+      This function would be useful when you know which option is needed but you
+      don't know which object(s) have it.
+      If you already know the source object, it would be more efficient to use
+      object retrieval functions.
+     </para>
+ 
+     <para>
+ <programlisting>
+ List *
+ GetForeignTableColumnOptions(Oid relid, AttrNumber attnum);
+ </programlisting>
+ 
+      This function returns per-column FDW options for a column with given
+      relation oid and attribute number in the form of list of
+      <structname>DefElem</structname>.
+     </para>
+ 
+     <para>
       Some object types have name-based functions.
      </para>
  
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index a7d30a1..df0e373 100644
*** a/src/backend/foreign/foreign.c
--- b/src/backend/foreign/foreign.c
*************** GetForeignTable(Oid relid)
*** 248,253 ****
--- 248,361 ----
  
  
  /*
+  * If an option entry which matches the given name was found in the given
+  * list, returns a copy of arg string, otherwise returns NULL.
+  */
+ static char *
+ get_options_value(List *options, const char *optname)
+ {
+ 	ListCell   *lc;
+ 
+ 	/* Find target option from the list. */
+ 	foreach (lc, options)
+ 	{
+ 		DefElem	   *def = lfirst(lc);
+ 
+ 		if (strcmp(def->defname, optname) == 0)
+ 			return pstrdup(strVal(def->arg));
+ 	}
+ 
+ 	return NULL;
+ }
+ 
+ /*
+  * Returns a copy of the value of specified option with searching the option
+  * from appropriate catalog.  If an option was stored in multiple object
+  * levels, one in the finest-grained object level is used; lookup order is:
+  *   1) pg_attribute (only when attnum != InvalidAttrNumber)
+  *   2) pg_foreign_table
+  *   3) pg_user_mapping
+  *   4) pg_foreign_server
+  *   5) pg_foreign_data_wrapper
+  * This priority rule would be useful in most cases using FDW options.
+  *
+  * If attnum was InvalidAttrNumber, we don't retrieve FDW optiosn from
+  * pg_attribute.attfdwoptions.
+  */
+ char *
+ GetFdwOptionValue(Oid relid, AttrNumber attnum, const char *optname)
+ {
+ 	ForeignTable   *table = NULL;
+ 	UserMapping	   *user = NULL;
+ 	ForeignServer  *server = NULL;
+ 	ForeignDataWrapper  *wrapper = NULL;
+ 	char		   *value;
+ 
+ 	/* Do we need to use pg_attribute.attfdwoptions too? */
+ 	if (attnum != InvalidAttrNumber)
+ 	{
+ 		value = get_options_value(GetForeignColumnOptions(relid, attnum),
+ 								  optname);
+ 		if (value != NULL)
+ 			return value;
+ 	}
+ 
+ 	table = GetForeignTable(relid);
+ 	value = get_options_value(table->options, optname);
+ 	if (value != NULL)
+ 		return value;
+ 
+ 	user = GetUserMapping(GetOuterUserId(), table->serverid);
+ 	value = get_options_value(user->options, optname);
+ 	if (value != NULL)
+ 		return value;
+ 
+ 	server = GetForeignServer(table->serverid);
+ 	value = get_options_value(server->options, optname);
+ 	if (value != NULL)
+ 		return value;
+ 
+ 	wrapper = GetForeignDataWrapper(server->fdwid);
+ 	value = get_options_value(wrapper->options, optname);
+ 	if (value != NULL)
+ 		return value;
+ 
+ 	return NULL;
+ }
+ 
+ 
+ /*
+  * GetForeignColumnOptions - Get attfdwoptions of given relation/attnum as
+  * list of DefElem.
+  */
+ List *
+ GetForeignColumnOptions(Oid relid, AttrNumber attnum)
+ {
+ 	List	   *options;
+ 	HeapTuple	tp;
+ 	Datum		datum;
+ 	bool		isnull;
+ 
+ 	tp = SearchSysCache2(ATTNUM,
+ 						 ObjectIdGetDatum(relid),
+ 						 Int16GetDatum(attnum));
+ 	if (!HeapTupleIsValid(tp))
+ 		elog(ERROR, "cache lookup failed for attribute %d of relation %u", attnum, relid);
+ 	datum = SysCacheGetAttr(ATTNUM,
+ 							tp,
+ 							Anum_pg_attribute_attfdwoptions,
+ 							&isnull);
+ 	if (isnull)
+ 		options = NIL;
+ 	else
+ 		options = untransformRelOptions(datum);
+ 
+ 	ReleaseSysCache(tp);
+ 
+ 	return options;
+ }
+ 
+ /*
   * GetFdwRoutine - call the specified foreign-data wrapper handler routine
   * to get its FdwRoutine struct.
   */
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 2c436ae..b6c8d5b 100644
*** a/src/include/foreign/foreign.h
--- b/src/include/foreign/foreign.h
*************** extern ForeignDataWrapper *GetForeignDat
*** 75,80 ****
--- 75,83 ----
  extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
  							bool missing_ok);
  extern ForeignTable *GetForeignTable(Oid relid);
+ extern char *GetFdwOptionValue(Oid relid, AttrNumber attnum,
+ 							const char *optname);
+ extern List *GetForeignColumnOptions(Oid relid, AttrNumber attnum);
  
  extern Oid	get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok);
  extern Oid	get_foreign_server_oid(const char *servername, bool missing_ok);
pgsql_fdw.patchtext/plain; name=pgsql_fdw.patchDownload
diff --git a/contrib/Makefile b/contrib/Makefile
index 0c238aa..e09e61e 100644
*** a/contrib/Makefile
--- b/contrib/Makefile
*************** SUBDIRS = \
*** 41,46 ****
--- 41,47 ----
  		pgbench		\
  		pgcrypto	\
  		pgrowlocks	\
+ 		pgsql_fdw	\
  		pgstattuple	\
  		seg		\
  		spi		\
diff --git a/contrib/README b/contrib/README
index a1d42a1..d3fa211 100644
*** a/contrib/README
--- b/contrib/README
*************** pgrowlocks -
*** 158,163 ****
--- 158,167 ----
  	A function to return row locking information
  	by Tatsuo Ishii <ishii@sraoss.co.jp>
  
+ pgsql_fdw -
+ 	Foreign-data wrapper for external PostgreSQL servers.
+ 	by Shigeru Hanada <shigeru.hanada@gmail.com>
+ 
  pgstattuple -
  	Functions to return statistics about "dead" tuples and free
  	space within a table
diff --git a/contrib/pgsql_fdw/.gitignore b/contrib/pgsql_fdw/.gitignore
index ...0854728 .
*** a/contrib/pgsql_fdw/.gitignore
--- b/contrib/pgsql_fdw/.gitignore
***************
*** 0 ****
--- 1,4 ----
+ # Generated subdirectories
+ /results/
+ *.o
+ *.so
diff --git a/contrib/pgsql_fdw/Makefile b/contrib/pgsql_fdw/Makefile
index ...6943409 .
*** a/contrib/pgsql_fdw/Makefile
--- b/contrib/pgsql_fdw/Makefile
***************
*** 0 ****
--- 1,22 ----
+ # pgsql_fdw/Makefile
+ 
+ MODULE_big = pgsql_fdw
+ OBJS	= pgsql_fdw.o option.o deparse.o connection.o
+ PG_CPPFLAGS = -I$(libpq_srcdir)
+ SHLIB_LINK = $(libpq)
+ 
+ EXTENSION = pgsql_fdw
+ DATA = pgsql_fdw--1.0.sql
+ 
+ REGRESS = pgsql_fdw
+ 
+ ifdef USE_PGXS
+ PG_CONFIG = pg_config
+ PGXS := $(shell $(PG_CONFIG) --pgxs)
+ include $(PGXS)
+ else
+ subdir = contrib/pgsql_fdw
+ top_builddir = ../..
+ include $(top_builddir)/src/Makefile.global
+ include $(top_srcdir)/contrib/contrib-global.mk
+ endif
diff --git a/contrib/pgsql_fdw/connection.c b/contrib/pgsql_fdw/connection.c
index ...e2d8d2a .
*** a/contrib/pgsql_fdw/connection.c
--- b/contrib/pgsql_fdw/connection.c
***************
*** 0 ****
--- 1,504 ----
+ /*-------------------------------------------------------------------------
+  *
+  * connection.c
+  *		  Connection management for pgsql_fdw
+  *
+  * Portions Copyright (c) 2011, PostgreSQL Global Development Group
+  *
+  * IDENTIFICATION
+  *		  pgsql_fdw/connection.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ 
+ #include "catalog/pg_type.h"
+ #include "foreign/foreign.h"
+ #include "funcapi.h"
+ #include "libpq-fe.h"
+ #include "mb/pg_wchar.h"
+ #include "miscadmin.h"
+ #include "utils/array.h"
+ #include "utils/builtins.h"
+ #include "utils/hsearch.h"
+ #include "utils/memutils.h"
+ #include "utils/resowner.h"
+ #include "utils/tuplestore.h"
+ 
+ #include "pgsql_fdw.h"
+ #include "connection.h"
+ 
+ /* ============================================================================
+  * Connection management functions
+  * ==========================================================================*/
+ 
+ /*
+  * Connection cache entry managed with hash table.
+  */
+ typedef struct ConnCacheEntry
+ {
+ 	/* hash key must be first */
+ 	Oid				serverid;	/* oid of foreign server */
+ 	Oid				userid;		/* oid of local user */
+ 
+ 	int				refs;		/* reference counter */
+ 	PGconn		   *conn;		/* foreign server connection */
+ } ConnCacheEntry;
+ 
+ /*
+  * Hash table which is used to cache connection to PostgreSQL servers, will be
+  * initialized before first attempt to connect PostgreSQL server by the backend.
+  */
+ static HTAB *FSConnectionHash;
+ 
+ /* ----------------------------------------------------------------------------
+  * prototype of private functions
+  * --------------------------------------------------------------------------*/
+ static void
+ cleanup_connection(ResourceReleasePhase phase,
+ 				   bool isCommit,
+ 				   bool isTopLevel,
+ 				   void *arg);
+ static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
+ /*
+  * Get a PGconn which can be used to execute foreign query on the remote
+  * PostgreSQL server with the user's authorization.  If this was the first
+  * request for the server, new connection is established.
+  */
+ PGconn *
+ GetConnection(ForeignServer *server, UserMapping *user)
+ {
+ 	bool			found;
+ 	ConnCacheEntry *entry;
+ 	ConnCacheEntry	key;
+ 	PGconn		   *conn = NULL;
+ 
+ 	/* initialize connection cache if it isn't */
+ 	if (FSConnectionHash == NULL)
+ 	{
+ 		HASHCTL		ctl;
+ 
+ 		/* hash key is a pair of oids: serverid and userid */
+ 		MemSet(&ctl, 0, sizeof(ctl));
+ 		ctl.keysize = sizeof(Oid) + sizeof(Oid);
+ 		ctl.entrysize = sizeof(ConnCacheEntry);
+ 		ctl.hash = tag_hash;
+ 		ctl.match = memcmp;
+ 		ctl.keycopy = memcpy;
+ 		/* allocate FSConnectionHash in the cache context */
+ 		ctl.hcxt = CacheMemoryContext;
+ 		FSConnectionHash = hash_create("Foreign Connections", 32,
+ 									   &ctl,
+ 									   HASH_ELEM | HASH_CONTEXT |
+ 									   HASH_FUNCTION | HASH_COMPARE |
+ 									   HASH_KEYCOPY);
+ 	}
+ 
+ 	/* Create key value for the entry. */
+ 	MemSet(&key, 0, sizeof(key));
+ 	key.serverid = server->serverid;
+ 	key.userid = GetOuterUserId();
+ 
+ 	/* Is there any cached and valid connection with such key? */
+ 	entry = hash_search(FSConnectionHash, &key, HASH_ENTER, &found);
+ 	if (found)
+ 	{
+ 		if (entry->conn != NULL)
+ 		{
+ 			entry->refs++;
+ 			elog(DEBUG1,
+ 				 "reuse connection %u/%u (%d)",
+ 				 entry->serverid,
+ 				 entry->userid,
+ 				 entry->refs);
+ 			return entry->conn;
+ 		}
+ 
+ 		/*
+ 		 * Connection cache entry was found but connection in it is invalid.
+ 		 * We reuse entry to store newly established connection later.
+ 		 */
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * Use ResourceOwner to clean the connection up on error including
+ 		 * user interrupt.
+ 		 */
+ 		elog(DEBUG1,
+ 			 "create entry for %u/%u (%d)",
+ 			 entry->serverid,
+ 			 entry->userid,
+ 			 entry->refs);
+ 		entry->refs = 0;
+ 		entry->conn = NULL;
+ 		RegisterResourceReleaseCallback(cleanup_connection, entry);
+ 	}
+ 
+ 	/*
+ 	 * Here we have to establish new connection.
+ 	 * Use PG_TRY block to ensure closing connection on error.
+ 	 */
+ 	PG_TRY();
+ 	{
+ 		/* Connect to the foreign PostgreSQL server */
+ 		conn = connect_pg_server(server, user);
+ 
+ 		/*
+ 		 * Initialize the cache entry to keep new connection.
+ 		 * Note: key items of entry has been initialized in
+ 		 * hash_search(HASH_ENTER).
+ 		 */
+ 		entry->refs = 1;
+ 		entry->conn = conn;
+ 		elog(DEBUG1,
+ 			 "connected to %u/%u (%d)",
+ 			 entry->serverid,
+ 			 entry->userid,
+ 			 entry->refs);
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		PQfinish(conn);
+ 		entry->refs = 0;
+ 		entry->conn = NULL;
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	return conn;
+ }
+ 
+ /*
+  * For non-superusers, insist that the connstr specify a password.	This
+  * prevents a password from being picked up from .pgpass, a service file,
+  * the environment, etc.  We don't want the postgres user's passwords
+  * to be accessible to non-superusers.
+  */
+ static void
+ check_conn_params(const char **keywords, const char **values)
+ {
+ 	int			i;
+ 
+ 	/* no check required if superuser */
+ 	if (superuser())
+ 		return;
+ 
+ 	/* ok if params contain a non-empty password */
+ 	for (i = 0; keywords[i] != NULL; i++)
+ 	{
+ 		if (strcmp(keywords[i], "password") == 0 && values[i][0] != '\0')
+ 			return;
+ 	}
+ 
+ 	ereport(ERROR,
+ 		  (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
+ 		   errmsg("password is required"),
+ 		   errdetail("Non-superusers must provide a password in the connection string.")));
+ }
+ 
+ static PGconn *
+ connect_pg_server(ForeignServer *server, UserMapping *user)
+ {
+ 	const char	   *conname = server->servername;
+ 	PGconn		   *conn;
+ 	PGresult	   *res;
+ 	const char	  **all_keywords;
+ 	const char	  **all_values;
+ 	const char	  **keywords;
+ 	const char	  **values;
+ 	int				n;
+ 	int				i, j;
+ 
+ 	/*
+ 	 * Construct connection params from generic options of ForeignServer and
+ 	 * UserMapping.  Those two object hold only libpq options.
+ 	 * Extra 3 items are for:
+ 	 *   *) fallback_application_name
+ 	 *   *) client_encoding
+ 	 *   *) NULL termination (end marker)
+ 	 *
+ 	 * Note: We don't omit any parameters even target database might be older
+ 	 * than local, because unexpected parameters are just ignored.
+ 	 */
+ 	n = list_length(server->options) + list_length(user->options) + 3;
+ 	all_keywords = (const char **) palloc(sizeof(char *) * n);
+ 	all_values = (const char **) palloc(sizeof(char *) * n);
+ 	keywords = (const char **) palloc(sizeof(char *) * n);
+ 	values = (const char **) palloc(sizeof(char *) * n);
+ 	n = 0;
+ 	n += ExtractConnectionOptions(server->options,
+ 								  all_keywords + n, all_values + n);
+ 	n += ExtractConnectionOptions(user->options,
+ 								  all_keywords + n, all_values + n);
+ 	all_keywords[n] = all_values[n] = NULL;
+ 
+ 	for (i = 0, j = 0; all_keywords[i]; i++)
+ 	{
+ 		keywords[j] = all_keywords[i];
+ 		values[j] = all_values[i];
+ 		j++;
+ 	}
+ 
+ 	/* Use "pgsql_fdw" as fallback_application_name. */
+ 	keywords[j] = "fallback_application_name";
+ 	values[j++] = "pgsql_fdw";
+ 
+ 	/* Set client_encoding so that libpq can convert encoding properly. */
+ 	keywords[j] = "client_encoding";
+ 	values[j++] = GetDatabaseEncodingName();
+ 
+ 	keywords[j] = values[j] = NULL;
+ 	pfree(all_keywords);
+ 	pfree(all_values);
+ 
+ 	/* verify connection parameters and do connect */
+ 	check_conn_params(keywords, values);
+ 	conn = PQconnectdbParams(keywords, values, 0);
+ 	if (!conn || PQstatus(conn) != CONNECTION_OK)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),
+ 				 errmsg("could not connect to server \"%s\"", conname),
+ 				 errdetail("%s", PQerrorMessage(conn))));
+ 	pfree(keywords);
+ 	pfree(values);
+ 
+ 	/*
+ 	 * Check that non-superuser has used password to establish connection.
+ 	 * This check logic is based on dblink_security_check() in contrib/dblink.
+ 	 *
+ 	 * XXX Should we check this even if we don't provide unsafe version like
+ 	 * dblink_connect_u()?
+ 	 */
+ 	if (!superuser() && !PQconnectionUsedPassword(conn))
+ 	{
+ 		PQfinish(conn);
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
+ 				 errmsg("password is required"),
+ 				 errdetail("Non-superuser cannot connect if the server does not request a password."),
+ 				 errhint("Target server's authentication method must be changed.")));
+ 	}
+ 
+ 	/*
+ 	 * Start transaction to use cursor to retrieve data separately.
+ 	 */
+ 	res = PQexec(conn, "BEGIN");
+ 	if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK)
+ 		elog(ERROR, "could not start transaction");
+ 
+ 	return conn;
+ }
+ 
+ /*
+  * Mark the connection as "unused", and close it if the caller was the last
+  * user of the connection.
+  */
+ void
+ ReleaseConnection(PGconn *conn)
+ {
+ 	HASH_SEQ_STATUS		scan;
+ 	ConnCacheEntry	   *entry;
+ 
+ 	if (conn == NULL)
+ 		return;
+ 
+ 	/*
+ 	 * We need to scan sequentially since we use the address to find appropriate
+ 	 * PGconn from the hash table.
+ 	 */ 
+ 	hash_seq_init(&scan, FSConnectionHash);
+ 	while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ 	{
+ 		if (entry->conn == conn)
+ 			break;
+ 	}
+ 	if (entry != NULL)
+ 		hash_seq_term(&scan);
+ 
+ 	/*
+ 	 * If the released connection was an orphan, just close it.
+ 	 */
+ 	if (entry == NULL)
+ 	{
+ 		PQfinish(conn);
+ 		return;
+ 	}
+ 
+ 	/*
+ 	 * If the caller was the last referrer, unregister it from cache.
+ 	 * TODO: Note that sharing connections requires a mechanism to detect
+ 	 * change of FDW object to invalidate lasting connections.
+ 	 */
+ 	entry->refs--;
+ 	elog(DEBUG1,
+ 		 "connection %u/%u released (%d)",
+ 		 entry->serverid,
+ 		 entry->userid,
+ 		 entry->refs);
+ }
+ 
+ /*
+  * Clean the connection up via ResourceOwner.
+  */
+ static void
+ cleanup_connection(ResourceReleasePhase phase,
+ 				   bool isCommit,
+ 				   bool isTopLevel,
+ 				   void *arg)
+ {
+ 	ConnCacheEntry *entry = (ConnCacheEntry *) arg;
+ 
+ 	/* If the transaction was committed, don't close connections. */
+ 	if (isCommit)
+ 		return;
+ 
+ 	/*
+ 	 * We clean the connection up on post-lock because foreign connections are
+ 	 * backend-internal resource.
+ 	 */
+ 	if (phase != RESOURCE_RELEASE_AFTER_LOCKS)
+ 		return;
+ 
+ 	/*
+ 	 * We ignore cleanup for ResourceOwners other than transaction.  At this
+ 	 * point, such a ResourceOwner is only Portal. 
+ 	 */
+ 	if (CurrentResourceOwner != CurTransactionResourceOwner)
+ 		return;
+ 
+ 	/*
+ 	 * We don't care whether we are in TopTransaction or Subtransaction.
+ 	 * Anyway, we close the connection and reset the reference counter.
+ 	 */
+ 	if (entry->conn != NULL)
+ 	{
+ 		elog(DEBUG1,
+ 			 "closing connection %u/%u",
+ 			 entry->serverid,
+ 			 entry->userid);
+ 		PQfinish(entry->conn);
+ 		entry->refs = 0;
+ 		entry->conn = NULL;
+ 	}
+ 	else
+ 		elog(DEBUG1,
+ 			 "connection %u/%u already closed",
+ 			 entry->serverid,
+ 			 entry->userid);
+ }
+ 
+ /*
+  * Get list of connections currently active.
+  */
+ Datum pgsql_fdw_get_connections(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(pgsql_fdw_get_connections);
+ Datum
+ pgsql_fdw_get_connections(PG_FUNCTION_ARGS)
+ {
+ 	ReturnSetInfo	   *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ 	HASH_SEQ_STATUS		scan;
+ 	ConnCacheEntry	   *entry;
+ 	MemoryContext		oldcontext = CurrentMemoryContext;
+ 	Tuplestorestate	   *tuplestore;
+ 	TupleDesc			tupdesc;
+ 
+ 	/* We return list of connection with storing them in a Tuplestore. */
+ 	rsinfo->returnMode = SFRM_Materialize;
+ 	rsinfo->setResult = NULL;
+ 	rsinfo->setDesc = NULL;
+ 
+ 	/* Create tuplestore and copy of TupleDesc in per-query context. */
+ 	MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
+ 
+ 	tupdesc = CreateTemplateTupleDesc(2, false);
+ 	TupleDescInitEntry(tupdesc, 1, "srvid", OIDOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, 2, "usesysid", OIDOID, -1, 0);
+ 	rsinfo->setDesc = tupdesc;
+ 
+ 	tuplestore = tuplestore_begin_heap(false, false, work_mem);
+ 	rsinfo->setResult = tuplestore;
+ 
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	/*
+ 	 * We need to scan sequentially since we use the address to find
+ 	 * appropriate PGconn from the hash table.
+ 	 */ 
+ 	if (FSConnectionHash != NULL)
+ 	{
+ 		hash_seq_init(&scan, FSConnectionHash);
+ 		while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ 		{
+ 			Datum		values[2];
+ 			bool		nulls[2];
+ 			HeapTuple	tuple;
+ 
+ 			elog(DEBUG1, "found: %u/%u", entry->serverid, entry->userid);
+ 
+ 			/* Ignore inactive connections */
+ 			if (PQstatus(entry->conn) != CONNECTION_OK)
+ 				continue;
+ 
+ 			/*
+ 			 * Ignore other users' connections if current user isn't a
+ 			 * superuser.
+ 			 */
+ 			if (!superuser() && entry->userid != GetUserId())
+ 				continue;
+ 
+ 			values[0] = ObjectIdGetDatum(entry->serverid);
+ 			values[1] = ObjectIdGetDatum(entry->userid);
+ 			nulls[0] = false;
+ 			nulls[1] = false;
+ 
+ 			tuple = heap_formtuple(tupdesc, values, nulls);
+ 			tuplestore_puttuple(tuplestore, tuple);
+ 		}
+ 	}
+ 	tuplestore_donestoring(tuplestore);
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Discard persistent connection designated by given connection name.
+  */
+ Datum pgsql_fdw_disconnect(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(pgsql_fdw_disconnect);
+ Datum
+ pgsql_fdw_disconnect(PG_FUNCTION_ARGS)
+ {
+ 	Oid					serverid = PG_GETARG_OID(0);
+ 	Oid					userid = PG_GETARG_OID(1);
+ 	ConnCacheEntry		key;
+ 	ConnCacheEntry	   *entry = NULL;
+ 	bool				found;
+ 
+ 	/* Non-superuser can't discard other users' connection. */
+ 	if (!superuser() && userid != GetOuterUserId())
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
+ 				 errmsg("only superuser can discard other user's connection")));
+ 
+ 	/*
+ 	 * If no connection has been established, or no such connections, just
+ 	 * return "NG" to indicate nothing has done.
+ 	 */
+ 	if (FSConnectionHash == NULL)
+ 		PG_RETURN_TEXT_P(cstring_to_text("NG"));
+ 
+ 	key.serverid = serverid;
+ 	key.userid = userid;
+ 	entry = hash_search(FSConnectionHash, &key, HASH_FIND, &found);
+ 	if (!found)
+ 		PG_RETURN_TEXT_P(cstring_to_text("NG"));
+ 
+ 	/* Discard cached connection, and clear reference counter. */
+ 	PQfinish(entry->conn);
+ 	entry->refs = 0;
+ 	entry->conn = NULL;
+ 	elog(DEBUG1, "closed connection %u/%u", serverid, userid);
+ 
+ 	PG_RETURN_TEXT_P(cstring_to_text("OK"));
+ }
diff --git a/contrib/pgsql_fdw/connection.h b/contrib/pgsql_fdw/connection.h
index ...80dd3e0 .
*** a/contrib/pgsql_fdw/connection.h
--- b/contrib/pgsql_fdw/connection.h
***************
*** 0 ****
--- 1,25 ----
+ /*-------------------------------------------------------------------------
+  *
+  * connection.h
+  *		  Connection management for pgsql_fdw
+  *
+  * Portions Copyright (c) 2011, PostgreSQL Global Development Group
+  *
+  * IDENTIFICATION
+  *		  pgsql_fdw/connection.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ #ifndef CONNECTION_H
+ #define CONNECTION_H
+ 
+ #include "foreign/foreign.h"
+ #include "libpq-fe.h"
+ 
+ /*
+  * Connection management
+  */
+ PGconn *GetConnection(ForeignServer *server, UserMapping *user);
+ void ReleaseConnection(PGconn *conn);
+ 
+ #endif /* CONNECTION_H */
diff --git a/contrib/pgsql_fdw/deparse.c b/contrib/pgsql_fdw/deparse.c
index ...4dab232 .
*** a/contrib/pgsql_fdw/deparse.c
--- b/contrib/pgsql_fdw/deparse.c
***************
*** 0 ****
--- 1,352 ----
+ /*-------------------------------------------------------------------------
+  *
+  * deparse.c
+  *		  query deparser for PostgreSQL
+  *
+  * Copyright (c) 2011, PostgreSQL Global Development Group
+  *
+  * IDENTIFICATION
+  *		  pgsql_fdw/deparse.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ 
+ #include "access/transam.h"
+ #include "foreign/foreign.h"
+ #include "lib/stringinfo.h"
+ #include "nodes/nodeFuncs.h"
+ #include "nodes/nodes.h"
+ #include "nodes/makefuncs.h"
+ #include "optimizer/clauses.h"
+ #include "optimizer/var.h"
+ #include "parser/parsetree.h"
+ #include "utils/builtins.h"
+ #include "utils/lsyscache.h"
+ 
+ #include "pgsql_fdw.h"
+ 
+ /*
+  * Context for walk-through the expression tree.
+  */
+ typedef struct foreign_executable_cxt
+ {
+ 	PlannerInfo	   *root;
+ 	RelOptInfo	   *foreignrel;
+ } foreign_executable_cxt;
+ 
+ static bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr);
+ static bool foreign_expr_walker(Node *node, foreign_executable_cxt *context);
+ static bool is_proc_remotely_executable(Oid procid);
+ 
+ /*
+  * Deparse query representation into SQL statement which suits for remote
+  * PostgreSQL server.  Also some of quals in WHERE clause will be pushed down
+  * If they are safe to be evaluated on the remote side.
+  */
+ char *
+ deparseSql(Oid relid, PlannerInfo *root, RelOptInfo *baserel)
+ {
+ 	StringInfoData	foreign_relname;
+ 	StringInfoData	sql;			/* builder for SQL statement */
+ 	bool		first;
+ 	AttrNumber	attr;
+ 	List	   *attr_used = NIL;	/* List of AttNumber used in the query */
+ 	const char *nspname = NULL;		/* plain namespace name */
+ 	const char *relname = NULL;		/* plain relation name */
+ 	const char *q_nspname;			/* quoted namespace name */
+ 	const char *q_relname;			/* quoted relation name */
+ 	List	   *foreign_expr = NIL;	/* list of Expr* evaluated on remote */
+ 	int			i;
+ 	List	   *rtable = NIL;
+ 	List	   *context = NIL;
+ 
+ 	initStringInfo(&sql);
+ 	initStringInfo(&foreign_relname);
+ 
+ 	/*
+ 	 * First of all, determine which qual can be pushed down.
+ 	 *
+ 	 * The expressions which satisfy is_foreign_expr() are deparsed into WHERE
+ 	 * clause of result SQL string, and they could be removed from PlanState
+ 	 * to avoid duplicate evaluation at ExecScan().
+ 	 *
+ 	 * We never change the quals in the Plan node, because this execution might
+ 	 * be for a PREPAREd statement, thus the quals in the Plan node might be
+ 	 * reused to construct another PlanState for subsequent EXECUTE statement.
+ 	 *
+ 	 * We do this before deparsing SELECT clause because attributes which are
+ 	 * not used in neither reltargetlist nor baserel->baserestrictinfo, quals
+ 	 * evaluated on local, can be replaced with literal "NULL" in the SELECT
+ 	 * clause to reduce overhead of tuple handling tuple and data transfer.
+ 	 */
+ 	if (baserel->baserestrictinfo != NIL)
+ 	{
+ 		ListCell   *lc;
+ 		List	   *local_qual = NIL;
+ 
+ 		foreach (lc, baserel->baserestrictinfo)
+ 		{
+ 			RestrictInfo   *ri = (RestrictInfo *) lfirst(lc);
+ 
+ 			/* Determine whether the qual can be pushed down or not. */
+ 			if (is_foreign_expr(root, baserel, ri->clause))
+ 				foreign_expr = lappend(foreign_expr, ri->clause);
+ 			else
+ 			{
+ 				List	   *attrs;
+ 
+ 				/*
+ 				 * We need to know which attributes are used in qual evaluated
+ 				 * on the local server, because they should be listed in the
+ 				 * SELECT clause of remote query.  We can ignore attributes
+ 				 * which are referenced only in ORDER BY/GROUP BY clause because
+ 				 * such attributes has already been kept in reltargetlist.
+ 				 */
+ 				attrs = pull_var_clause((Node *) ri->clause,
+ 										PVC_RECURSE_AGGREGATES,
+ 										PVC_RECURSE_PLACEHOLDERS);
+ 				attr_used = list_union(attr_used, attrs);
+ 
+ 				/*
+ 				 * Save the RestrictInfo to replace baserel->baserestrictinfo
+ 				 * afterward.
+ 				 */
+ 				local_qual = lappend(local_qual, ri);
+ 			}
+ 		}
+ 
+ 		/*
+ 		 * Remove quals which are going to evaluated on the foreign server to
+ 		 * avoid overhead of duplicated evaluation.
+ 		 */
+ 		baserel->baserestrictinfo = local_qual;
+ 	}
+ 
+ 	/*
+ 	 * Determine foreign relation's qualified name.  This is necessary for
+ 	 * FROM clause and SELECT clause.
+ 	 */
+ 	nspname = GetFdwOptionValue(relid, InvalidAttrNumber, "nspname");
+ 	if (nspname == NULL)
+ 		nspname = get_namespace_name(get_rel_namespace(relid));
+ 	q_nspname = quote_identifier(nspname);
+ 
+ 	relname = GetFdwOptionValue(relid, InvalidAttrNumber, "relname");
+ 	if (relname == NULL)
+ 		relname = get_rel_name(relid);
+ 	q_relname = quote_identifier(relname);
+ 
+ 	appendStringInfo(&foreign_relname, "%s.%s", q_nspname, q_relname);
+ 
+ 	/*
+ 	 * Create context for the deparse.
+ 	 * We need multi-relation context without PlanState, and alias of each
+ 	 * RangeTblEntry should be modified to be valid on remote side.
+ 	 * 
+ 	 * We skip first element of simple_rel_array.
+ 	 */
+ 	for (i = 1; i < root->simple_rel_array_size; i++)
+ 	{
+ 		RangeTblEntry *rte = copyObject(root->simple_rte_array[i]);
+ 		rtable = lappend(rtable, rte);
+ 	}
+ 	context = deparse_context_for_rtelist(rtable);
+ 
+ 	/*
+ 	 * deparse SELECT clause
+ 	 *
+ 	 * List attributes which are in either target list or local restriction.
+ 	 * Unused attributes are replace with literal "NULL" for optimization.
+ 	 */
+ 	appendStringInfo(&sql, "SELECT ");
+ 	attr_used = list_union(attr_used, baserel->reltargetlist);
+ 	first = true;
+ 	for (attr = 1; attr <= baserel->max_attr; attr++)
+ 	{
+ 		RangeTblEntry *rte = root->simple_rte_array[baserel->relid];
+ 		Var		   *var = NULL;
+ 		ListCell   *lc;
+ 
+ 		/* Ignore droppped attributes. */
+ 		if (get_rte_attribute_is_dropped(rte, attr))
+ 			continue;
+ 
+ 		if (!first)
+ 			appendStringInfo(&sql, ", ");
+ 		first = false;
+ 
+ 		/*
+ 		 * We use linear search here, but it wouldn't be problem since
+ 		 * attr_used seems to not become so large.
+ 		 */
+ 		foreach (lc, attr_used)
+ 		{
+ 			var = lfirst(lc);
+ 			if (var->varattno == attr)
+ 				break;
+ 			var = NULL;
+ 		}
+ 		if (var != NULL)
+ 			appendStringInfo(&sql, "%s",
+ 				deparse_expression((Node *) var, context, false, false));
+ 		else
+ 			appendStringInfo(&sql, "NULL");
+ 	}
+ 	appendStringInfoChar(&sql, ' ');
+ 
+ 	/*
+ 	 * deparse FROM clause
+ 	 */
+ 	appendStringInfo(&sql, "FROM %s", foreign_relname.data);
+ 
+ 	/*
+ 	 * deparse WHERE clause
+ 	 */
+ 	if (foreign_expr != NIL)
+ 	{
+ 		Node	   *node;
+ 
+ 		node = (Node *) make_ands_explicit(foreign_expr);
+ 		appendStringInfo(&sql, " WHERE %s ",
+ 			deparse_expression(node, context, false, false));
+ 		list_free(foreign_expr);
+ 		foreign_expr = NIL;
+ 	}
+ 
+ 	return sql.data;
+ }
+ 
+ /*
+  * Returns true if expr is safe to be evaluated on the foreign server.
+  */
+ static bool
+ is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr)
+ {
+ 	foreign_executable_cxt	context;
+ 	context.root = root;
+ 	context.foreignrel = baserel;
+ 
+ 	/*
+ 	 * An expression which includes any mutable function can't be pushed down
+ 	 * because it's result is not stable.  For example, pushing now() down to
+ 	 * remote side would cause confusion from the clock offset.
+ 	 * If we have routine mapping infrastructure in future release, we will be
+ 	 * able to choose function to be pushed down in finer granularity.
+ 	 */
+ 	if (contain_mutable_functions((Node *) expr))
+ 		return false;
+ 
+ 	/*
+ 	 * Check that the expression consists of nodes which are known as safe to
+ 	 * be pushed down.
+ 	 */
+ 	if (foreign_expr_walker((Node *) expr, &context))
+ 		return false;
+ 
+ 	return true;
+ }
+ 
+ /*
+  * Return true if node includes any node which is not known as safe to be
+  * pushed down.
+  */
+ static bool
+ foreign_expr_walker(Node *node, foreign_executable_cxt *context)
+ {
+ 	if (node == NULL)
+ 		return false;
+ 
+ 	switch (nodeTag(node))
+ 	{
+ 		case T_Const:
+ 		case T_ArrayExpr:
+ 		case T_BoolExpr:
+ 		case T_NullTest:
+ 		case T_DistinctExpr:
+ 		case T_ScalarArrayOpExpr:
+ 			/*
+ 			 * These type of nodes are known as safe to be pushed down.
+ 			 * Of course the subtre of the node, if any, should be checked
+ 			 * continuously at the tail of this function.
+ 			 */
+ 			break;
+ 		case T_Param:
+ 			/*
+ 			 * Only external parameters can be pushed down.:
+ 			 */
+ 			{
+ 				if (((Param *) node)->paramkind != PARAM_EXTERN)
+ 					return true;
+ 			}
+ 			break;
+ 		case T_OpExpr:
+ 			/*
+ 			 * Operators which use non-immutable function can't be pushed down.
+ 			 */
+ 			{
+ 				OpExpr	   *oe = (OpExpr *) node;
+ 
+ 				if (!is_proc_remotely_executable(oe->opfuncid))
+ 					return true;
+ 
+ 				/* operands are checked later */
+ 			}
+ 			break;
+ 		case T_FuncExpr:
+ 			/*
+ 			 * Non-immutable functions can't be pushed down.
+ 			 */
+ 			{
+ 				FuncExpr   *fe = (FuncExpr *) node;
+ 
+ 				if (!is_proc_remotely_executable(fe->funcid))
+ 					return true;
+ 
+ 				/* operands are checked later */
+ 			}
+ 			break;
+ 		case T_Var:
+ 			/*
+ 			 * Var can be pushed down if it is in the foreign table.
+ 			 * XXX Var of other relation can be here?
+ 			 */
+ 			{
+ 				Var	   *var = (Var *) node;
+ 				foreign_executable_cxt *f_context;
+ 
+ 				f_context = (foreign_executable_cxt *) context;
+ 				if (var->varno != f_context->foreignrel->relid ||
+ 					var->varlevelsup != 0)
+ 					return true;
+ 			}
+ 			break;
+ 		default:
+ 			{
+ 				ereport(DEBUG3,
+ 						(errmsg("expression is too complex"),
+ 						 errdetail("%s", nodeToString(node))));
+ 				return true;
+ 			}
+ 			break;
+ 	}
+ 
+ 	return expression_tree_walker(node, foreign_expr_walker, context);
+ }
+ 
+ /*
+  * Return true if func is known as safe to be pushed down if all of the
+  * arguments are also known as safe.
+  */
+ static bool
+ is_proc_remotely_executable(Oid procid)
+ {
+ 	/*
+ 	 * User-defined procedures can't be pushed down.
+ 	 */
+ 	if (procid >= FirstNormalObjectId)
+ 		return false;
+ 
+ 	return true;
+ }
+ 
diff --git a/contrib/pgsql_fdw/expected/pgsql_fdw.out b/contrib/pgsql_fdw/expected/pgsql_fdw.out
index ...af72154 .
*** a/contrib/pgsql_fdw/expected/pgsql_fdw.out
--- b/contrib/pgsql_fdw/expected/pgsql_fdw.out
***************
*** 0 ****
--- 1,406 ----
+ -- ===================================================================
+ -- create FDW objects
+ -- ===================================================================
+ CREATE EXTENSION pgsql_fdw;
+ CREATE SERVER loopback1 FOREIGN DATA WRAPPER pgsql_fdw;
+ CREATE SERVER loopback2 FOREIGN DATA WRAPPER pgsql_fdw
+   OPTIONS (dbname 'contrib_regression');
+ CREATE USER MAPPING FOR public SERVER loopback1
+ 	OPTIONS (user 'value', password 'value');
+ CREATE USER MAPPING FOR public SERVER loopback2;
+ CREATE FOREIGN TABLE ft1 (
+ 	c1 int NOT NULL,
+ 	c2 int NOT NULL,
+ 	c3 text,
+ 	c4 timestamptz,
+ 	c5 timestamp
+ ) SERVER loopback2;
+ CREATE FOREIGN TABLE ft2 (
+ 	c1 int NOT NULL,
+ 	c2 int NOT NULL,
+ 	c3 text,
+ 	c4 timestamptz,
+ 	c5 timestamp
+ ) SERVER loopback2;
+ -- ===================================================================
+ -- create objects used through FDW
+ -- ===================================================================
+ CREATE SCHEMA "S 1";
+ CREATE TABLE "S 1"."T 1" (
+ 	c1 int NOT NULL,
+ 	c2 int NOT NULL,
+ 	c3 text,
+ 	c4 timestamptz,
+ 	c5 timestamp,
+ 	CONSTRAINT t1_pkey PRIMARY KEY (c1)
+ );
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "t1_pkey" for table "T 1"
+ CREATE TABLE "S 1"."T 2" (
+ 	c1 int NOT NULL,
+ 	c2 text,
+ 	CONSTRAINT t2_pkey PRIMARY KEY (c1)
+ );
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "t2_pkey" for table "T 2"
+ BEGIN;
+ TRUNCATE "S 1"."T 1";
+ INSERT INTO "S 1"."T 1"
+ 	SELECT id,
+ 	       id % 10,
+ 	       to_char(id, 'FM00000'),
+ 	       '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval,
+ 	       '1970-01-01'::timestamp + ((id % 100) || ' days')::interval
+ 	FROM generate_series(1, 1000) id;
+ TRUNCATE "S 1"."T 2";
+ INSERT INTO "S 1"."T 2"
+ 	SELECT id,
+ 	       'AAA' || to_char(id, 'FM000')
+ 	FROM generate_series(1, 100) id;
+ COMMIT;
+ -- ===================================================================
+ -- tests for pgsql_fdw_validator
+ -- ===================================================================
+ ALTER FOREIGN DATA WRAPPER pgsql_fdw OPTIONS (host 'value');    -- ERRROR
+ ERROR:  invalid option "host"
+ HINT:  Valid options in this context are: 
+ -- requiressl, krbsrvname and gsslib are omitted because they depend on
+ -- configure option
+ ALTER SERVER loopback1 OPTIONS (
+ 	--authtype 'value',
+ 	service 'value',
+ 	connect_timeout 'value',
+ 	dbname 'value',
+ 	host 'value',
+ 	hostaddr 'value',
+ 	port 'value',
+ 	--client_encoding 'value',
+ 	--tty 'value',
+ 	options 'value',
+ 	application_name 'value',
+ 	--fallback_application_name 'value',
+ 	keepalives 'value',
+ 	keepalives_idle 'value',
+ 	keepalives_interval 'value',
+ 	-- requiressl 'value',
+ 	sslmode 'value',
+ 	sslcert 'value',
+ 	sslkey 'value',
+ 	sslrootcert 'value',
+ 	sslcrl 'value'
+ 	--requirepeer 'value',
+ 	-- krbsrvname 'value',
+ 	-- gsslib 'value',
+ 	--replication 'value'
+ );
+ ALTER SERVER loopback1 OPTIONS (user 'value');                  -- ERROR
+ ERROR:  invalid option "user"
+ HINT:  Valid options in this context are: service, connect_timeout, dbname, host, hostaddr, port, options, application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, sslmode, sslcert, sslkey, sslrootcert, sslcrl, requirepeer, min_cursor_rows, fetch_count
+ ALTER SERVER loopback2 OPTIONS (ADD min_cursor_rows '0');
+ ALTER SERVER loopback2 OPTIONS (ADD fetch_count '2');
+ ALTER USER MAPPING FOR public SERVER loopback1
+ 	OPTIONS (DROP user, DROP password);
+ ALTER USER MAPPING FOR public SERVER loopback1
+ 	OPTIONS (host 'value');                                     -- ERROR
+ ERROR:  invalid option "host"
+ HINT:  Valid options in this context are: user, password
+ ALTER FOREIGN TABLE ft1 OPTIONS (nspname 'S 1', relname 'T 1');
+ ALTER FOREIGN TABLE ft2 OPTIONS (nspname 'S 1', relname 'T 1', min_cursor_rows '10', fetch_count '100');
+ ALTER FOREIGN TABLE ft1 OPTIONS (invalid 'value');              -- ERROR
+ ERROR:  invalid option "invalid"
+ HINT:  Valid options in this context are: nspname, relname, min_cursor_rows, fetch_count
+ ALTER FOREIGN TABLE ft1 OPTIONS (min_cursor_rows 'a');          -- ERROR
+ ERROR:  invalid value for min_cursor_rows: "a"
+ ALTER FOREIGN TABLE ft1 OPTIONS (min_cursor_rows '-1');         -- ERROR
+ ERROR:  invalid value for min_cursor_rows: "-1"
+ ALTER FOREIGN TABLE ft1 OPTIONS (fetch_count 'a');              -- ERROR
+ ERROR:  invalid value for fetch_count: "a"
+ ALTER FOREIGN TABLE ft1 OPTIONS (fetch_count '0');              -- ERROR
+ ERROR:  invalid value for fetch_count: "0"
+ ALTER FOREIGN TABLE ft1 OPTIONS (fetch_count '-1');             -- ERROR
+ ERROR:  invalid value for fetch_count: "-1"
+ \dew+
+                                          List of foreign-data wrappers
+    Name    |  Owner   |      Handler      |      Validator      | Access privileges | FDW Options | Description 
+ -----------+----------+-------------------+---------------------+-------------------+-------------+-------------
+  pgsql_fdw | postgres | pgsql_fdw_handler | pgsql_fdw_validator |                   |             | 
+ (1 row)
+ 
+ \des+
+                                                                                                                                                                                                 List of foreign servers
+    Name    |  Owner   | Foreign-data wrapper | Access privileges | Type | Version |                                                                                                                                                     FDW Options                                                                                                                                                      | Description 
+ -----------+----------+----------------------+-------------------+------+---------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------
+  loopback1 | postgres | pgsql_fdw            |                   |      |         | (service 'value', connect_timeout 'value', dbname 'value', host 'value', hostaddr 'value', port 'value', options 'value', application_name 'value', keepalives 'value', keepalives_idle 'value', keepalives_interval 'value', sslmode 'value', sslcert 'value', sslkey 'value', sslrootcert 'value', sslcrl 'value') | 
+  loopback2 | postgres | pgsql_fdw            |                   |      |         | (dbname 'contrib_regression', min_cursor_rows '0', fetch_count '2')                                                                                                                                                                                                                                                  | 
+ (2 rows)
+ 
+ \deu+
+         List of user mappings
+   Server   | User name | FDW Options 
+ -----------+-----------+-------------
+  loopback1 | public    | 
+  loopback2 | public    | 
+ (2 rows)
+ 
+ \det+
+                                                List of foreign tables
+  Schema | Table |  Server   |                               FDW Options                               | Description 
+ --------+-------+-----------+-------------------------------------------------------------------------+-------------
+  public | ft1   | loopback2 | (nspname 'S 1', relname 'T 1')                                          | 
+  public | ft2   | loopback2 | (nspname 'S 1', relname 'T 1', min_cursor_rows '10', fetch_count '100') | 
+ (2 rows)
+ 
+ -- ===================================================================
+ -- simple queries
+ -- ===================================================================
+ -- single table, with/without alias
+ EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
+                               QUERY PLAN                              
+ ----------------------------------------------------------------------
+  Limit
+    Output: c1, c2, c3, c4, c5
+    ->  Sort
+          Output: c1, c2, c3, c4, c5
+          Sort Key: ft1.c3, ft1.c1
+          ->  Foreign Scan on public.ft1
+                Output: c1, c2, c3, c4, c5
+                Remote SQL: SELECT c1, c2, c3, c4, c5 FROM "S 1"."T 1"
+ (8 rows)
+ 
+ SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
+  c1  | c2 |  c3   |              c4              |            c5            
+ -----+----+-------+------------------------------+--------------------------
+  101 |  1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970
+  102 |  2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970
+  103 |  3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970
+  104 |  4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970
+  105 |  5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970
+  106 |  6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970
+  107 |  7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970
+  108 |  8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970
+  109 |  9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970
+  110 |  0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970
+ (10 rows)
+ 
+ EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
+                               QUERY PLAN                              
+ ----------------------------------------------------------------------
+  Limit
+    Output: c1, c2, c3, c4, c5
+    ->  Sort
+          Output: c1, c2, c3, c4, c5
+          Sort Key: t1.c3, t1.c1
+          ->  Foreign Scan on public.ft1 t1
+                Output: c1, c2, c3, c4, c5
+                Remote SQL: SELECT c1, c2, c3, c4, c5 FROM "S 1"."T 1"
+ (8 rows)
+ 
+ SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
+  c1  | c2 |  c3   |              c4              |            c5            
+ -----+----+-------+------------------------------+--------------------------
+  101 |  1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970
+  102 |  2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970
+  103 |  3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970
+  104 |  4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970
+  105 |  5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970
+  106 |  6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970
+  107 |  7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970
+  108 |  8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970
+  109 |  9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970
+  110 |  0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970
+ (10 rows)
+ 
+ -- with WHERE clause
+ SELECT * FROM ft1 t1 WHERE t1.c1 = 101;
+  c1  | c2 |  c3   |              c4              |            c5            
+ -----+----+-------+------------------------------+--------------------------
+  101 |  1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970
+ (1 row)
+ 
+ -- aggregate
+ SELECT COUNT(*) FROM ft1 t1;
+  count 
+ -------
+   1000
+ (1 row)
+ 
+ -- join two tables
+ SELECT t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
+  c1  
+ -----
+  101
+  102
+  103
+  104
+  105
+  106
+  107
+  108
+  109
+  110
+ (10 rows)
+ 
+ -- subquery
+ SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1;
+  c1 | c2 |  c3   |              c4              |            c5            
+ ----+----+-------+------------------------------+--------------------------
+   1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970
+   2 |  2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970
+   3 |  3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970
+   4 |  4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970
+   5 |  5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970
+   6 |  6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970
+   7 |  7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970
+   8 |  8 | 00008 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970
+   9 |  9 | 00009 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970
+  10 |  0 | 00010 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970
+ (10 rows)
+ 
+ -- subquery+MAX
+ SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1;
+   c1  | c2 |  c3   |              c4              |            c5            
+ ------+----+-------+------------------------------+--------------------------
+  1000 |  0 | 01000 | Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970
+ (1 row)
+ 
+ -- used in CTE
+ WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1;
+  c1 | c2 |  c3   |              c4              
+ ----+----+-------+------------------------------
+   1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST
+   2 |  2 | 00002 | Sat Jan 03 00:00:00 1970 PST
+   3 |  3 | 00003 | Sun Jan 04 00:00:00 1970 PST
+   4 |  4 | 00004 | Mon Jan 05 00:00:00 1970 PST
+   5 |  5 | 00005 | Tue Jan 06 00:00:00 1970 PST
+   6 |  6 | 00006 | Wed Jan 07 00:00:00 1970 PST
+   7 |  7 | 00007 | Thu Jan 08 00:00:00 1970 PST
+   8 |  8 | 00008 | Fri Jan 09 00:00:00 1970 PST
+   9 |  9 | 00009 | Sat Jan 10 00:00:00 1970 PST
+  10 |  0 | 00010 | Sun Jan 11 00:00:00 1970 PST
+ (10 rows)
+ 
+ -- fixed values
+ SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1;
+  ?column? | ?column? 
+ ----------+----------
+  fixed    | 
+ (1 row)
+ 
+ -- ===================================================================
+ -- parameterized queries
+ -- ===================================================================
+ -- simple join
+ PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
+ EXPLAIN (COSTS false) EXECUTE st1(1, 2);
+                                        QUERY PLAN                                       
+ ----------------------------------------------------------------------------------------
+  Nested Loop
+    ->  Foreign Scan on ft1 t1
+          Remote SQL: SELECT NULL, NULL, c3, NULL, NULL FROM "S 1"."T 1" WHERE (c1 = 1) 
+    ->  Foreign Scan on ft2 t2
+          Remote SQL: SELECT NULL, NULL, c3, NULL, NULL FROM "S 1"."T 1" WHERE (c1 = 2) 
+ (5 rows)
+ 
+ EXECUTE st1(1, 1);
+   c3   |  c3   
+ -------+-------
+  00001 | 00001
+ (1 row)
+ 
+ EXECUTE st1(101, 101);
+   c3   |  c3   
+ -------+-------
+  00101 | 00101
+ (1 row)
+ 
+ -- subquery using stable function (can't be pushed down)
+ PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c4) = 6) ORDER BY c1;
+ EXPLAIN (COSTS false) EXECUTE st2(10, 20);
+                                                                    QUERY PLAN                                                                   
+ ------------------------------------------------------------------------------------------------------------------------------------------------
+  Sort
+    Sort Key: t1.c1
+    ->  Nested Loop
+          Join Filter: (t1.c3 = t2.c3)
+          ->  HashAggregate
+                ->  Foreign Scan on ft2 t2
+                      Filter: (date_part('dow'::text, c4) = 6::double precision)
+                      Remote SQL: DECLARE pgsql_fdw_cursor_6 SCROLL CURSOR FOR SELECT NULL, NULL, c3, c4, NULL FROM "S 1"."T 1" WHERE (c1 > 10) 
+          ->  Foreign Scan on ft1 t1
+                Remote SQL: SELECT c1, c2, c3, c4, c5 FROM "S 1"."T 1" WHERE (c1 < 20) 
+ (10 rows)
+ 
+ EXECUTE st2(10, 20);
+  c1 | c2 |  c3   |              c4              |            c5            
+ ----+----+-------+------------------------------+--------------------------
+  16 |  6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970
+ (1 row)
+ 
+ EXECUTE st1(101, 101);
+   c3   |  c3   
+ -------+-------
+  00101 | 00101
+ (1 row)
+ 
+ -- subquery using immutable function (can be pushed down)
+ PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c5) = 6) ORDER BY c1;
+ EXPLAIN (COSTS false) EXECUTE st3(10, 20);
+                                                                              QUERY PLAN                                                                             
+ --------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  Sort
+    Sort Key: t1.c1
+    ->  Hash Join
+          Hash Cond: (t1.c3 = t2.c3)
+          ->  Foreign Scan on ft1 t1
+                Remote SQL: SELECT c1, c2, c3, c4, c5 FROM "S 1"."T 1" WHERE (c1 < 20) 
+          ->  Hash
+                ->  HashAggregate
+                      ->  Foreign Scan on ft2 t2
+                            Remote SQL: SELECT NULL, NULL, c3, NULL, NULL FROM "S 1"."T 1" WHERE ((c1 > 10) AND (date_part('dow'::text, c5) = 6::double precision)) 
+ (10 rows)
+ 
+ EXECUTE st3(10, 20);
+  c1 | c2 |  c3   |              c4              |            c5            
+ ----+----+-------+------------------------------+--------------------------
+  16 |  6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970
+ (1 row)
+ 
+ EXECUTE st3(20, 30);
+  c1 | c2 |  c3   |              c4              |            c5            
+ ----+----+-------+------------------------------+--------------------------
+  23 |  3 | 00023 | Sat Jan 24 00:00:00 1970 PST | Sat Jan 24 00:00:00 1970
+ (1 row)
+ 
+ -- cleanup
+ DEALLOCATE st1;
+ DEALLOCATE st2;
+ DEALLOCATE st3;
+ -- ===================================================================
+ -- connection management
+ -- ===================================================================
+ SELECT srvname, usename FROM pgsql_fdw_connections;
+   srvname  | usename  
+ -----------+----------
+  loopback2 | postgres
+ (1 row)
+ 
+ SELECT pgsql_fdw_disconnect(srvid, usesysid) FROM pgsql_fdw_get_connections();
+  pgsql_fdw_disconnect 
+ ----------------------
+  OK
+ (1 row)
+ 
+ SELECT srvname, usename FROM pgsql_fdw_connections;
+  srvname | usename 
+ ---------+---------
+ (0 rows)
+ 
+ -- ===================================================================
+ -- cleanup
+ -- ===================================================================
+ DROP EXTENSION pgsql_fdw CASCADE;
+ NOTICE:  drop cascades to 6 other objects
+ DETAIL:  drop cascades to server loopback1
+ drop cascades to user mapping for public
+ drop cascades to server loopback2
+ drop cascades to user mapping for public
+ drop cascades to foreign table ft1
+ drop cascades to foreign table ft2
diff --git a/contrib/pgsql_fdw/option.c b/contrib/pgsql_fdw/option.c
index ...610daad .
*** a/contrib/pgsql_fdw/option.c
--- b/contrib/pgsql_fdw/option.c
***************
*** 0 ****
--- 1,262 ----
+ /*-------------------------------------------------------------------------
+  *
+  * option.c
+  *		  FDW option handling
+  *
+  * Copyright (c) 2011, PostgreSQL Global Development Group
+  *
+  * IDENTIFICATION
+  *		  pgsql_fdw/option.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ 
+ #include "access/reloptions.h"
+ #include "catalog/pg_foreign_data_wrapper.h"
+ #include "catalog/pg_foreign_server.h"
+ #include "catalog/pg_foreign_table.h"
+ #include "catalog/pg_user_mapping.h"
+ #include "fmgr.h"
+ #include "foreign/foreign.h"
+ #include "lib/stringinfo.h"
+ #include "miscadmin.h"
+ 
+ #include "pgsql_fdw.h"
+ 
+ /*
+  * SQL functions
+  */
+ extern Datum pgsql_fdw_validator(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(pgsql_fdw_validator);
+ 
+ /*
+  * Describes the valid options for objects that use this wrapper.
+  */
+ typedef struct PgsqlFdwOption
+ {
+ 	const char *optname;
+ 	Oid			optcontext;		/* Oid of catalog in which options may appear */
+ 	bool		is_libpq_opt;	/* true if it's used in libpq */
+ } PgsqlFdwOption;
+ 
+ /*
+  * Valid options for pgsql_fdw.
+  */
+ static PgsqlFdwOption valid_options[] = {
+ 
+ 	/*
+ 	 * Options for libpq connection.
+ 	 * Note: This list should be updated along with PQconninfoOptions in
+ 	 * interfaces/libpq/fe-connect.c, so the order is kept as is.
+ 	 *
+ 	 * Some useless libpq connection options are not accepted by pgsql_fdw:
+ 	 *   authtype: no longer used
+ 	 *   client_encoding: set to local database encoding automatically
+ 	 *   tty: no longer used
+ 	 *   fallback_application_name: fixed to "pgsql_fdw"
+ 	 *   replication: pgsql_fdw never be replication client
+ 	 */
+ 	{"service", ForeignServerRelationId, true},
+ 	{"user", UserMappingRelationId, true},
+ 	{"password", UserMappingRelationId, true},
+ 	{"connect_timeout", ForeignServerRelationId, true},
+ 	{"dbname", ForeignServerRelationId, true},
+ 	{"host", ForeignServerRelationId, true},
+ 	{"hostaddr", ForeignServerRelationId, true},
+ 	{"port", ForeignServerRelationId, true},
+ 	{"options", ForeignServerRelationId, true},
+ 	{"application_name", ForeignServerRelationId, true},
+ 	{"keepalives", ForeignServerRelationId, true},
+ 	{"keepalives_idle", ForeignServerRelationId, true},
+ 	{"keepalives_interval", ForeignServerRelationId, true},
+ 	{"keepalives_count", ForeignServerRelationId, true},
+ #ifdef USE_SSL
+ 	{"requiressl", ForeignServerRelationId, true},
+ #endif
+ 	{"sslmode", ForeignServerRelationId, true},
+ 	{"sslcert", ForeignServerRelationId, true},
+ 	{"sslkey", ForeignServerRelationId, true},
+ 	{"sslrootcert", ForeignServerRelationId, true},
+ 	{"sslcrl", ForeignServerRelationId, true},
+ 	{"requirepeer", ForeignServerRelationId, true},
+ #if defined(KRB5) || defined(ENABLE_GSS) || defined(ENABLE_SSPI)
+ 	{"krbsrvname", ForeignServerRelationId, true},
+ #endif
+ #if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
+ 	{"gsslib", ForeignServerRelationId, true},
+ #endif
+ 
+ 	/*
+ 	 * Options for translation of object names.
+ 	 * Note: Per-column options are not supported in 9.1, so we can't translate
+ 	 * column name.
+ 	 */
+ 	{"nspname", ForeignTableRelationId, false},
+ 	{"relname", ForeignTableRelationId, false},
+ 
+ 	/*
+ 	 * Options for cursor behavior.
+ 	 * These options can be overridden by smaller objects.
+ 	 */
+ 	{"min_cursor_rows", ForeignTableRelationId, false},
+ 	{"min_cursor_rows", ForeignServerRelationId, false},
+ 	{"fetch_count", ForeignTableRelationId, false},
+ 	{"fetch_count", ForeignServerRelationId, false},
+ 
+ 	/* Sentinel */
+ 	{NULL, InvalidOid, false}
+ };
+ 
+ /*
+  * Helper functions
+  */
+ static bool is_valid_option(const char *optname, Oid context);
+ 
+ /*
+  * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER,
+  * USER MAPPING or FOREIGN TABLE that uses pgsql_fdw.
+  *
+  * Raise an ERROR if the option or its value is considered invalid.
+  */
+ Datum
+ pgsql_fdw_validator(PG_FUNCTION_ARGS)
+ {
+ 	List	   *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
+ 	Oid			catalog = PG_GETARG_OID(1);
+ 	ListCell   *cell;
+ 
+ 	/*
+ 	 * Check that only options supported by pgsql_fdw, and allowed for the
+ 	 * current object type, are given.
+ 	 */
+ 	foreach(cell, options_list)
+ 	{
+ 		DefElem    *def = (DefElem *) lfirst(cell);
+ 
+ 		if (!is_valid_option(def->defname, catalog))
+ 		{
+ 			PgsqlFdwOption *opt;
+ 			StringInfoData buf;
+ 
+ 			/*
+ 			 * Unknown option specified, complain about it. Provide a hint
+ 			 * with list of valid options for the object.
+ 			 */
+ 			initStringInfo(&buf);
+ 			for (opt = valid_options; opt->optname; opt++)
+ 			{
+ 				if (catalog == opt->optcontext)
+ 					appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
+ 									 opt->optname);
+ 			}
+ 
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
+ 					 errmsg("invalid option \"%s\"", def->defname),
+ 					 errhint("Valid options in this context are: %s",
+ 							 buf.data)));
+ 		}
+ 
+ 		/* min_cursor_rows be zero or positive digit number. */
+ 		if (strcmp(def->defname, "min_cursor_rows") == 0)
+ 		{
+ 			long value;
+ 			char *p = NULL;
+ 
+ 			value = strtol(strVal(def->arg), &p, 10);
+ 			if (*p != '\0' || value < 0)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE),
+ 						 errmsg("invalid value for %s: \"%s\"",
+ 								def->defname, strVal(def->arg))));
+ 		}
+ 
+ 		/* fetch_count be positive digit number. */
+ 		if (strcmp(def->defname, "fetch_count") == 0)
+ 		{
+ 			long value;
+ 			char *p = NULL;
+ 
+ 			value = strtol(strVal(def->arg), &p, 10);
+ 			if (*p != '\0' || value < 1)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE),
+ 						 errmsg("invalid value for %s: \"%s\"",
+ 								def->defname, strVal(def->arg))));
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * We don't care option-specific limitation here; they will be validated at
+ 	 * the execution time. 
+ 	 */
+ 
+ 	PG_RETURN_VOID();
+ }
+ 
+ /*
+  * Check if the provided option is one of the valid options.
+  * context is the Oid of the catalog holding the object the option is for.
+  */
+ static bool
+ is_valid_option(const char *optname, Oid context)
+ {
+ 	PgsqlFdwOption *opt;
+ 
+ 	/*
+ 	 * If the option was other than libpq option, look up option table and
+ 	 * determine valid context.
+ 	 */
+ 	for (opt = valid_options; opt->optname; opt++)
+ 	{
+ 		if (context == opt->optcontext && strcmp(opt->optname, optname) == 0)
+ 			return true;
+ 	}
+ 	return false;
+ }
+ 
+ /*
+  * Check if the provided option is one of the valid options.
+  * context is the Oid of the catalog holding the object the option is for.
+  */
+ static bool
+ is_libpq_option(const char *optname)
+ {
+ 	PgsqlFdwOption *opt;
+ 
+ 	/*
+ 	 * If the option was other than libpq option, look up option table and
+ 	 * determine valid context.
+ 	 */
+ 	for (opt = valid_options; opt->optname; opt++)
+ 	{
+ 		if (strcmp(opt->optname, optname) == 0 && opt->is_libpq_opt)
+ 			return true;
+ 	}
+ 	return false;
+ }
+ 
+ /*
+  * Generate key-value arrays which includes only libpq options from the list
+  * which contains any kind of options.
+  */
+ int
+ ExtractConnectionOptions(List *defelems, const char **keywords, const char **values)
+ {
+ 	ListCell *lc;
+ 	int i;
+ 
+ 	i = 0;
+ 	foreach(lc, defelems)
+ 	{
+ 		DefElem *d = (DefElem *) lfirst(lc);
+ 		if (is_libpq_option(d->defname))
+ 		{
+ 			keywords[i] = d->defname;
+ 			values[i] = strVal(d->arg);
+ 			i++;
+ 		}
+ 	}
+ 	return i;
+ }
diff --git a/contrib/pgsql_fdw/pgsql_fdw--1.0.sql b/contrib/pgsql_fdw/pgsql_fdw--1.0.sql
index ...87dfa29 .
*** a/contrib/pgsql_fdw/pgsql_fdw--1.0.sql
--- b/contrib/pgsql_fdw/pgsql_fdw--1.0.sql
***************
*** 0 ****
--- 1,36 ----
+ /* contrib/pgsql_fdw/pgsql_fdw--1.0.sql */
+ 
+ CREATE FUNCTION pgsql_fdw_handler()
+ RETURNS fdw_handler
+ AS 'MODULE_PATHNAME'
+ LANGUAGE C STRICT;
+ 
+ CREATE FUNCTION pgsql_fdw_validator(text[], oid)
+ RETURNS void
+ AS 'MODULE_PATHNAME'
+ LANGUAGE C STRICT;
+ 
+ CREATE FOREIGN DATA WRAPPER pgsql_fdw
+   HANDLER pgsql_fdw_handler
+   VALIDATOR pgsql_fdw_validator;
+ 
+ /* connection management functions and view */
+ CREATE FUNCTION pgsql_fdw_get_connections(out srvid oid, out usesysid oid)
+ RETURNS SETOF record
+ AS 'MODULE_PATHNAME'
+ LANGUAGE C STRICT;
+ 
+ CREATE FUNCTION pgsql_fdw_disconnect(oid, oid)
+ RETURNS text
+ AS 'MODULE_PATHNAME'
+ LANGUAGE C STRICT;
+ 
+ CREATE VIEW pgsql_fdw_connections AS
+ SELECT c.srvid srvid,
+        s.srvname srvname,
+        c.usesysid usesysid,
+        pg_get_userbyid(c.usesysid) usename
+   FROM pgsql_fdw_get_connections() c
+            JOIN pg_catalog.pg_foreign_server s ON (s.oid = c.srvid);
+ GRANT SELECT ON pgsql_fdw_connections TO public;
+ 
diff --git a/contrib/pgsql_fdw/pgsql_fdw.c b/contrib/pgsql_fdw/pgsql_fdw.c
index ...614916c .
*** a/contrib/pgsql_fdw/pgsql_fdw.c
--- b/contrib/pgsql_fdw/pgsql_fdw.c
***************
*** 0 ****
--- 1,882 ----
+ /*-------------------------------------------------------------------------
+  *
+  * pgsql_fdw.c
+  *		  foreign-data wrapper for remote PostgreSQL servers.
+  *
+  * Copyright (c) 2011, PostgreSQL Global Development Group
+  *
+  * IDENTIFICATION
+  *		  pgsql_fdw/pgsql_fdw.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ #include "fmgr.h"
+ 
+ #include "catalog/pg_foreign_server.h"
+ #include "catalog/pg_foreign_table.h"
+ #include "commands/explain.h"
+ #include "foreign/fdwapi.h"
+ #include "funcapi.h"
+ #include "miscadmin.h"
+ #include "optimizer/cost.h"
+ #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
+ #include "utils/rel.h"
+ 
+ #include "pgsql_fdw.h"
+ #include "connection.h"
+ 
+ PG_MODULE_MAGIC;
+ 
+ /*
+  * Default fetch count for cursor mode.  This can be override by fetch_count
+  * FDW option.
+  */
+ #define DEFAULT_FETCH_COUNT			1000
+ 
+ /*
+  * Default minimum estimated row count for cursor mode.  This can be override
+  * by min_cursor_rows FDW option.
+  * */
+ #define DEFAULT_MIN_CURSOR_ROWS		1000
+ 
+ /*
+  * Cost to establish a connection.
+  * XXX: should be configurable per server?
+  */
+ #define CONNECTION_COSTS			100.0
+ 
+ /*
+  * Cost to transfer 1 byte from remote server.
+  * XXX: should be configurable per server?
+  */
+ #define TRANSFER_COSTS_PER_BYTE		0.001
+ 
+ /*
+  * Cursors which are used together in a local query require different name, so
+  * we use simple incremental name for that purpose.  We don't care wrap around
+  * of cursor_id because it's hard to imagine that 2^32 cursors are used in a
+  * query.
+  */
+ #define	CURSOR_NAME_FORMAT		"pgsql_fdw_cursor_%u"
+ static uint32 cursor_id = 0;
+ 
+ /*
+  * Index of FDW-private items stored in FdwPlan.
+  */
+ enum FdwPrivateIndex {
+ 	FdwPrivateSelectSql,
+ 
+ 	/* Items for cursor mode */
+ 	FdwPrivateDeclareSql,
+ 	FdwPrivateFetchSql,
+ 	FdwPrivateResetSql,
+ 	FdwPrivateCloseSql,
+ 
+ 	/* # of elements stored in the list fdw_private */
+ 	FdwPrivateNum,
+ };
+ 
+ /*
+  * This macro can be used in the executor to determine whether the scan is
+  * using CURSOR or not.
+  */
+ #define USE_CURSOR(fdwplan)	\
+ 	(list_length((fdwplan)->fdw_private) > FdwPrivateDeclareSql)
+ 
+ /*
+  * Describes an execution state of a foreign scan against a foreign table
+  * using pgsql_fdw. 
+  */
+ typedef struct PgsqlFdwExecutionState
+ {
+ 	FdwPlan	   *fdwplan;		/* FDW-specific planning information */
+ 	PGconn	   *conn;			/* connection for the scan */
+ 
+ 	Oid		   *param_types;	/* type array of external parameter */
+ 	const char **param_values;	/* value array of external parameter */
+ 
+ 	int			attnum;			/* # of non-dropped attribute */
+ 	char	  **col_values;		/* column value buffer */
+ 	AttInMetadata *attinmeta;	/* attribute metadata */
+ 
+ 	Tuplestorestate *tuples;	/* result of the scan, partial in cursor mode */
+ 	bool		cursor_opened;	/* true if cursor is opened */
+ } PgsqlFdwExecutionState;
+ 
+ /*
+  * SQL functions
+  */
+ extern Datum pgsql_fdw_handler(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(pgsql_fdw_handler);
+ 
+ /*
+  * FDW callback routines
+  */
+ static FdwPlan *pgsqlPlanForeignScan(Oid foreigntableid,
+ 									 PlannerInfo *root,
+ 									 RelOptInfo *baserel);
+ static void pgsqlExplainForeignScan(ForeignScanState *node, ExplainState *es);
+ static void pgsqlBeginForeignScan(ForeignScanState *node, int eflags);
+ static TupleTableSlot *pgsqlIterateForeignScan(ForeignScanState *node);
+ static void pgsqlReScanForeignScan(ForeignScanState *node);
+ static void pgsqlEndForeignScan(ForeignScanState *node);
+ 
+ /*
+  * Helper functions
+  */
+ static void estimate_costs(PlannerInfo *root,
+ 						   RelOptInfo *baserel,
+ 						   const char *sql,
+ 						   Oid serverid,
+ 						   Cost *startup_cost,
+ 						   Cost *total_cost);
+ static void execute_query(ForeignScanState *node);
+ static PGresult *fetch_result(ForeignScanState *node);
+ static void store_result(ForeignScanState *node, PGresult *res);
+ 
+ /*
+  * Foreign-data wrapper handler function: return a struct with pointers
+  * to my callback routines.
+  */
+ Datum
+ pgsql_fdw_handler(PG_FUNCTION_ARGS)
+ {
+ 	FdwRoutine *fdwroutine = makeNode(FdwRoutine);
+ 
+ 	fdwroutine->PlanForeignScan = pgsqlPlanForeignScan;
+ 	fdwroutine->ExplainForeignScan = pgsqlExplainForeignScan;
+ 	fdwroutine->BeginForeignScan = pgsqlBeginForeignScan;
+ 	fdwroutine->IterateForeignScan = pgsqlIterateForeignScan;
+ 	fdwroutine->ReScanForeignScan = pgsqlReScanForeignScan;
+ 	fdwroutine->EndForeignScan = pgsqlEndForeignScan;
+ 
+ 	PG_RETURN_POINTER(fdwroutine);
+ }
+ 
+ /*
+  * pgsqlPlanForeignScan
+  *		Create a FdwPlan for a scan on the foreign table
+  */
+ static FdwPlan *
+ pgsqlPlanForeignScan(Oid foreigntableid,
+ 					PlannerInfo *root,
+ 					RelOptInfo *baserel)
+ {
+ 	char	   *sql;
+ 	FdwPlan	   *fdwplan;
+ 	List	   *fdw_private = NIL;
+ 	const char *min_cursor_rows_str;
+ 	int	min_cursor_rows = DEFAULT_MIN_CURSOR_ROWS;
+ 	ForeignTable   *table;
+ 	ForeignServer  *server;
+ 
+ 	/* Construct FdwPlan with cost estimates */
+ 	fdwplan = makeNode(FdwPlan);
+ 	sql = deparseSql(foreigntableid, root, baserel);
+ 	table = GetForeignTable(foreigntableid);
+ 	server = GetForeignServer(table->serverid);
+ 	estimate_costs(root, baserel, sql, server->serverid,
+ 				   &fdwplan->startup_cost, &fdwplan->total_cost);
+ 
+ 	/*
+ 	 * Store plain SELECT statement in private area of FdwPlan.  This will be
+ 	 * used for executing remote query and explaining scan.
+ 	 */
+ 	fdw_private = list_make1(makeString(sql));
+ 
+ 	/*
+ 	 * We use SQL-level cursor when the result seems to be large, to limit
+ 	 * memory usage for the result set. Exceptionally we use simple SELECT if
+ 	 * min_cursor_rows is set to 0.
+ 	 */
+ 	min_cursor_rows_str = GetFdwOptionValue(foreigntableid,
+ 											InvalidAttrNumber,
+ 											"min_cursor_rows");
+ 	if (min_cursor_rows_str != NULL)
+ 		min_cursor_rows = strtol(min_cursor_rows_str, NULL, 10);
+ 
+ 	if (min_cursor_rows != 0 && baserel->rows >= min_cursor_rows)
+ 	{
+ 		char			name[128];	/* must be larger than format + 10 */
+ 		StringInfoData	cursor;
+ 		const char	   *fetch_count_str;
+ 		int				fetch_count = DEFAULT_FETCH_COUNT;
+ 
+ 		/* Use specified fetch_count instead of default value, if any. */
+ 		fetch_count_str = GetFdwOptionValue(foreigntableid,
+ 											InvalidAttrNumber,
+ 											"fetch_count");
+ 		if (fetch_count_str != NULL)
+ 			fetch_count = strtol(fetch_count_str, NULL, 10);
+ 		elog(DEBUG1,
+ 			 "relid=%u fetch_count=%d",
+ 			 foreigntableid,
+ 			 fetch_count);
+ 
+ 		/* We store some more information in FdwPlan to pass them beyond the
+ 		 * boundary between planner and executor.  Finally FdwPlan using cursor
+ 		 * would hold items below:
+ 		 *
+ 		 * 1) plain SELECT statement (already added above)
+ 		 * 2) SQL statement used to declare cursor
+ 		 * 3) SQL statement used to fetch rows from cursor
+ 		 * 4) SQL statement used to reset cursor 
+ 		 * 5) SQL statement used to close cursor
+ 		 *
+ 		 * These items are indexed with the enum FdwPrivateIndex, so an item
+ 		 * can be accessed directly via list_nth().  For example of FETCH
+ 		 * statement:
+ 		 *      list_nth(fdw_private, FdwPrivateFetchSql)
+ 		 */
+ 
+ 		/* Construct cursor name from sequential value */
+ 		sprintf(name, CURSOR_NAME_FORMAT, cursor_id++);
+ 
+ 		/* Construct statement to declare cursor */
+ 		initStringInfo(&cursor);
+ 		appendStringInfo(&cursor, "DECLARE %s SCROLL CURSOR FOR %s", name, sql);
+ 		fdw_private = lappend(fdw_private, makeString(cursor.data));
+ 
+ 		/* Construct statement to fetch rows from cursor */
+ 		initStringInfo(&cursor);
+ 		appendStringInfo(&cursor, "FETCH %d FROM %s", fetch_count, name);
+ 		fdw_private = lappend(fdw_private, makeString(cursor.data));
+ 
+ 		/* Construct statement to reset cursor */
+ 		initStringInfo(&cursor);
+ 		appendStringInfo(&cursor, "MOVE ABSOLUTE 0 FROM %s", name);
+ 		fdw_private = lappend(fdw_private, makeString(cursor.data));
+ 
+ 		/* Construct statement to close cursor */
+ 		initStringInfo(&cursor);
+ 		appendStringInfo(&cursor, "CLOSE %s", name);
+ 		fdw_private = lappend(fdw_private, makeString(cursor.data));
+ 	}
+ 
+ 	/* Store FDW private information into FdwPlan */
+ 	fdwplan->fdw_private = fdw_private;
+ 
+ 	return fdwplan;
+ }
+ 
+ /*
+  * pgsqlExplainForeignScan
+  *		Produce extra output for EXPLAIN
+  */
+ static void
+ pgsqlExplainForeignScan(ForeignScanState *node, ExplainState *es)
+ {
+ 	FdwPlan	   *fdwplan;
+ 	char	   *sql;
+ 
+ 	fdwplan = ((ForeignScan *) node->ss.ps.plan)->fdwplan;
+ 	if (USE_CURSOR(fdwplan))
+ 		sql = strVal(list_nth(fdwplan->fdw_private, FdwPrivateDeclareSql));
+ 	else
+ 		sql = strVal(list_nth(fdwplan->fdw_private, FdwPrivateSelectSql));
+ 	ExplainPropertyText("Remote SQL", sql, es);
+ }
+ 
+ /*
+  * pgsqlBeginForeignScan
+  *		Initiate access to a foreign PostgreSQL table.
+  */
+ static void
+ pgsqlBeginForeignScan(ForeignScanState *node, int eflags)
+ {
+ 	PgsqlFdwExecutionState *festate;
+ 	PGconn		   *conn;
+ 	Oid				relid;
+ 	ForeignTable   *table;
+ 	ForeignServer  *server;
+ 	UserMapping	   *user;
+ 	TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+ 
+ 	/*
+ 	 * Do nothing in EXPLAIN (no ANALYZE) case.  node->fdw_state stays NULL.
+ 	 */
+ 	if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
+ 		return;
+ 
+ 	/*
+ 	 * Save state in node->fdw_state.
+ 	 */
+ 	festate = (PgsqlFdwExecutionState *) palloc(sizeof(PgsqlFdwExecutionState));
+ 	festate->fdwplan = ((ForeignScan *) node->ss.ps.plan)->fdwplan;
+ 
+ 	/*
+ 	 * Get connection to the foreign server.  Connection manager would
+ 	 * establish new connection if necessary.
+ 	 */
+ 	relid = RelationGetRelid(node->ss.ss_currentRelation);
+ 	table = GetForeignTable(relid);
+ 	server = GetForeignServer(table->serverid);
+ 	user = GetUserMapping(GetOuterUserId(), server->serverid);
+ 	conn = GetConnection(server, user);
+ 	festate->conn = conn;
+ 
+ 	/* Result will be filled in first Iterate call. */
+ 	festate->tuples = NULL;
+ 	festate->cursor_opened = false;
+ 
+ 	/* Allocate buffers for column values. */
+ 	{
+ 		TupleDesc	tupdesc = slot->tts_tupleDescriptor;
+ 		festate->col_values = palloc(sizeof(char *) * tupdesc->natts);
+ 		festate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+ 	}
+ 
+ 	/* Allocate buffers for query parameters. */
+ 	{
+ 		ParamListInfo	params = node->ss.ps.state->es_param_list_info;
+ 		int				numParams = params ? params->numParams : 0;
+ 
+ 		if (numParams > 0)
+ 		{
+ 			festate->param_types = palloc0(sizeof(Oid) * numParams);
+ 			festate->param_values = palloc0(sizeof(char *) * numParams);
+ 		}
+ 		else
+ 		{
+ 			festate->param_types = NULL;
+ 			festate->param_values = NULL;
+ 		}
+ 	}
+ 
+ 
+ 	/* Store FDW-specific state into ForeignScanState */
+ 	node->fdw_state = (void *) festate;
+ 
+ 	return;
+ }
+ 
+ /*
+  * pgsqlIterateForeignScan
+  *		Retrieve next row from the result set, or clear tuple slot to indicate
+  *		EOF.
+  *
+  *		Note that using per-query context when retrieving tuples from
+  *		tuplestore to ensure that returned tuples can survive until next
+  *		iteration because the tuple is released implicitly via ExecClearTuple.
+  *		Retrieving a tuple from tuplestore in CurrentMemoryContext (it's a
+  *		per-tuple context), ExecClearTuple will free dangling pointer.
+  */
+ static TupleTableSlot *
+ pgsqlIterateForeignScan(ForeignScanState *node)
+ {
+ 	PgsqlFdwExecutionState *festate;
+ 	TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+ 	PGresult	   *res;
+ 	MemoryContext	oldcontext = CurrentMemoryContext;
+ 
+ 	festate = (PgsqlFdwExecutionState *) node->fdw_state;
+ 
+ 
+ 	/*
+ 	 * If this is the first call after Begin, we need to execute remote query.
+ 	 * If the query needs cursor, we declare a cursor at first call and fetch
+ 	 * from it in later calls.
+ 	 */
+ 	if (festate->tuples == NULL && !festate->cursor_opened)
+ 		execute_query(node);
+ 
+ 	/*
+ 	 * If enough tuples are left in tuplestore, just return next tuple from it.
+ 	 */
+ 	MemoryContextSwitchTo(node->ss.ps.state->es_query_cxt);
+ 	if (tuplestore_gettupleslot(festate->tuples, true, false, slot))
+ 	{
+ 		MemoryContextSwitchTo(oldcontext);
+ 		return slot;
+ 	}
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	/* If the scan doesn't use cursor, the scan has been done. */
+ 	if (!USE_CURSOR(festate->fdwplan))
+ 	{
+ 		ExecClearTuple(slot);
+ 		return slot;
+ 	}
+ 
+ 	/*
+ 	 * Here we need to clear partial result and fetch next bunch of tuples from
+ 	 * from the cursor for the scan.  If the fetch returns no tuple, the scan
+ 	 * has reached the end.
+ 	 */
+ 	res = fetch_result(node);
+ 	PG_TRY();
+ 	{
+ 		store_result(node, res);
+ 		PQclear(res);
+ 		res = NULL;
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		PQclear(res);
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	/*
+ 	 * If we got more tuples from the server cursor, return next tuple from
+ 	 * tuplestore.
+ 	 */
+ 	MemoryContextSwitchTo(node->ss.ps.state->es_query_cxt);
+ 	if (tuplestore_gettupleslot(festate->tuples, true, false, slot))
+ 	{
+ 		MemoryContextSwitchTo(oldcontext);
+ 		return slot;
+ 	}
+ 	MemoryContextSwitchTo(oldcontext);
+ 
+ 	/* We don't have any result even in remote server cursor. */
+ 	ExecClearTuple(slot);
+ 	return slot;
+ }
+ 
+ /*
+  * pgsqlReScanForeignScan
+  *   - Restart this scan by resetting fetch location.
+  */
+ static void
+ pgsqlReScanForeignScan(ForeignScanState *node)
+ {
+ 	List	   *fdw_private;
+ 	char	   *sql;
+ 	PGconn	   *conn;
+ 	PGresult   *res;
+ 	PgsqlFdwExecutionState *festate;
+ 
+ 	festate = (PgsqlFdwExecutionState *) node->fdw_state;
+ 
+ 	/* Discard fetch results if any. */
+ 	if (festate->tuples != NULL)
+ 	{
+ 		tuplestore_end(festate->tuples);
+ 		festate->tuples = NULL;
+ 	}
+ 
+ 	/* Reset cursor */
+ 	if (USE_CURSOR(festate->fdwplan))
+ 	{
+ 		fdw_private = festate->fdwplan->fdw_private;
+ 		conn = festate->conn;
+ 		sql = strVal(list_nth(fdw_private, FdwPrivateResetSql));
+ 		res = PQexec(conn, sql);
+ 		PG_TRY();
+ 		{
+ 			if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK)
+ 			{
+ 				ereport(ERROR,
+ 						(errmsg("could not rewind cursor"),
+ 						 errdetail("%s", PQerrorMessage(conn)),
+ 						 errhint("%s", sql)));
+ 			}
+ 			PQclear(res);
+ 			res = NULL;
+ 		}
+ 		PG_CATCH();
+ 		{
+ 			PQclear(res);
+ 			PG_RE_THROW();
+ 		}
+ 		PG_END_TRY();
+ 	}
+ }
+ 
+ /*
+  * pgsqlEndForeignScan
+  *		Finish scanning foreign table and dispose objects used for this scan
+  */
+ static void
+ pgsqlEndForeignScan(ForeignScanState *node)
+ {
+ 	List	   *fdw_private;
+ 	char	   *sql;
+ 	PGconn	   *conn;
+ 	PGresult   *res;
+ 	PgsqlFdwExecutionState *festate;
+ 
+ 	festate = (PgsqlFdwExecutionState *) node->fdw_state;
+ 
+ 	/* if festate is NULL, we are in EXPLAIN; nothing to do */
+ 	if (festate == NULL)
+ 		return;
+ 
+ 	/* Discard fetch results */
+ 	if (festate->tuples != NULL)
+ 	{
+ 		tuplestore_end(festate->tuples);
+ 		festate->tuples = NULL;
+ 	}
+ 
+ 	/* Close cursor */
+ 	if (USE_CURSOR(festate->fdwplan) && festate->cursor_opened)
+ 	{
+ 		fdw_private = festate->fdwplan->fdw_private;
+ 		conn = festate->conn;
+ 		sql = strVal(list_nth(fdw_private, FdwPrivateCloseSql));
+ 		res = PQexec(conn, sql);
+ 		PG_TRY();
+ 		{
+ 			if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK)
+ 			{
+ 				ereport(ERROR,
+ 						(errmsg("could not close cursor"),
+ 						 errdetail("%s", PQerrorMessage(conn)),
+ 						 errhint("%s", sql)));
+ 			}
+ 			PQclear(res);
+ 			res = NULL;
+ 		}
+ 		PG_CATCH();
+ 		{
+ 			PQclear(res);
+ 			PG_RE_THROW();
+ 		}
+ 		PG_END_TRY();
+ 	}
+ 
+ 	ReleaseConnection(festate->conn);
+ }
+ 
+ /*
+  * Estimate costs of scanning a foreign table.
+  */
+ static void
+ estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+ 			   const char *sql, Oid serverid,
+ 			   Cost *startup_cost, Cost *total_cost)
+ {
+ 	ForeignServer  *server;
+ 	UserMapping	   *user;
+ 	PGconn		   *conn = NULL;
+ 	PGresult	   *res = NULL;
+ 	StringInfoData  buf;
+ 	char		   *plan;
+ 	char		   *p;
+ 	char		   *endp;
+ 
+ 	/*
+ 	 * Get connection to the foreign server.  Connection manager would
+ 	 * establish new connection if necessary.
+ 	 */
+ 	server = GetForeignServer(serverid);
+ 	user = GetUserMapping(GetOuterUserId(), server->serverid);
+ 	conn = GetConnection(server, user);
+ 	initStringInfo(&buf);
+ 	appendStringInfo(&buf, "EXPLAIN (FORMAT YAML) %s", sql);
+ 
+ 	/* remove WHERE clause if the query uses parameter */
+ 	p = strchr(buf.data, '$');
+ 	if (p != NULL)
+ 	{
+ 		p = strstr(buf.data, "WHERE");
+ 		if (p != NULL)
+ 			*p = '\0';
+ 	}
+ 
+ 	PG_TRY();
+ 	{
+ 		res = PQexec(conn, buf.data);
+ 		if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK)
+ 		{
+ 			char *msg;
+ 
+ 			msg = pstrdup(PQerrorMessage(conn));
+ 			PQclear(res);
+ 			res = NULL;
+ 			ereport(ERROR,
+ 					(errmsg("could not execute EXPLAIN for cost estimation"),
+ 					 errdetail("%s", msg),
+ 					 errhint("%s", sql)));
+ 		}
+ 		plan = pstrdup(PQgetvalue(res, 0, 0));
+ 		PQclear(res);
+ 		res = NULL;
+ 		ReleaseConnection(conn);
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		PQclear(res);
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	/* extract startup cost from remote plan */
+ 	p = strstr(plan, "Startup Cost: ");
+ 	if (p != NULL)
+ 	{
+ 		p += strlen("Startup Cost: ");
+ 		*startup_cost = strtod(p, &endp);
+ 		if (*endp != '\n' && *endp != '\0')
+ 			elog(ERROR, "invalid plan format for startup cost%c", *endp);
+ 	}
+ 
+ 	/* extract total cost from remote plan */
+ 	p = strstr(plan, "Total Cost: ");
+ 	if (p != NULL)
+ 	{
+ 		p += strlen("Total Cost: ");
+ 		*total_cost = strtod(p, &endp);
+ 		if (*endp != '\n' && *endp != '\0')
+ 			elog(ERROR, "invalid plan format for total cost");
+ 	}
+ 
+ 	/* extract # of rows from remote plan */
+ 	p = strstr(plan, "Plan Rows: ");
+ 	if (p != NULL)
+ 	{
+ 		p += strlen("Plan Rows: ");
+ 		baserel->rows = strtod(p, &endp);
+ 		if (*endp != '\n' && *endp != '\0')
+ 			elog(ERROR, "invalid plan format for plan rows");
+ 	}
+ 
+ 	/* extract average width from remote plan */
+ 	p = strstr(plan, "Plan Width: ");
+ 	if (p != NULL)
+ 	{
+ 		p += strlen("Plan Width: ");
+ 		baserel->width = strtol(p, &endp, 10);
+ 		if (*endp != '\n' && *endp != '\0')
+ 			elog(ERROR, "invalid plan format for plan width");
+ 	}
+ 
+ 	/* TODO Selectivity of quals pushed down should be considered. */
+ 
+ 	/* add cost to establish connection. */
+ 	*startup_cost += CONNECTION_COSTS;
+ 	*total_cost += CONNECTION_COSTS;
+ 
+ 	/* add cost to transfer result. */
+ 	*total_cost += TRANSFER_COSTS_PER_BYTE * baserel->width * baserel->tuples;
+ 	*total_cost += cpu_tuple_cost * baserel->tuples;
+ }
+ 
+ /*
+  * Execute remote query with current parameters.
+  */
+ static void
+ execute_query(ForeignScanState *node)
+ {
+ 	FdwPlan		   *fdwplan;
+ 	PgsqlFdwExecutionState *festate;
+ 	ParamListInfo	params = node->ss.ps.state->es_param_list_info;
+ 	int				numParams = params ? params->numParams : 0;
+ 	Oid			   *types = NULL;
+ 	const char	  **values = NULL;
+ 	char		   *sql;
+ 	int				required_result;
+ 	PGconn		   *conn;
+ 	PGresult	   *res;
+ 
+ 	festate = (PgsqlFdwExecutionState *) node->fdw_state;
+ 	types = festate->param_types;
+ 	values = festate->param_values;
+ 
+ 	/*
+ 	 * Construct parameter array in text format.  We don't release memory for
+ 	 * the arrays explicitly, because the memory usage would not be very large,
+ 	 * and anyway they will be released in context cleanup.
+ 	 */
+ 	if (numParams > 0)
+ 	{
+ 		int i;
+ 
+ 		for (i = 0; i < numParams; i++)
+ 		{
+ 			types[i] = params->params[i].ptype;
+ 			if (params->params[i].isnull)
+ 				values[i] = NULL;
+ 			else
+ 			{
+ 				Oid			out_func_oid;	
+ 				bool		isvarlena;
+ 				FmgrInfo	func;
+ 
+ 				getTypeOutputInfo(types[i], &out_func_oid, &isvarlena);
+ 				fmgr_info(out_func_oid, &func);
+ 				values[i] = OutputFunctionCall(&func, params->params[i].value);
+ 			}
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Execute remote query with parameters.
+ 	 */
+ 	conn = festate->conn;
+ 	fdwplan = ((ForeignScan *) node->ss.ps.plan)->fdwplan;
+ 	if (USE_CURSOR(fdwplan))
+ 	{
+ 		sql = strVal(list_nth(fdwplan->fdw_private, FdwPrivateDeclareSql));
+ 		required_result = PGRES_COMMAND_OK;
+ 	}
+ 	else
+ 	{
+ 		sql = strVal(list_nth(fdwplan->fdw_private, FdwPrivateSelectSql));
+ 		required_result = PGRES_TUPLES_OK;
+ 	}
+ 	res = PQexecParams(conn, sql, numParams, types, values, NULL, NULL, 0);
+ 	PG_TRY();
+ 	{
+ 		/*
+ 		 * If the query has failed, reporting details is enough here.
+ 		 * Connection(s) which are used by this query (at least used by
+ 		 * pgsql_fdw) will be cleaned up by the foreign connection manager.
+ 		 */
+ 		if (res == NULL || PQresultStatus(res) != required_result)
+ 		{
+ 			ereport(ERROR,
+ 					(errmsg("could not execute foreign query"),
+ 					 errdetail("%s", PQerrorMessage(conn)),
+ 					 errhint("%s", sql)));
+ 		}
+ 
+ 		/* Fetch first bunch of result if we using cursor. */
+ 		if (USE_CURSOR(fdwplan))
+ 		{
+ 			/* Mark that this scan has opened a cursor. */
+ 			festate->cursor_opened = true;
+ 
+ 			/* Discard result of CURSOR statement and fetch first bunch. */
+ 			PQclear(res);
+ 			res = fetch_result(node);
+ 		}
+ 
+ 		/*
+ 		 * Store the result of the query into tuplestore.
+ 		 * We must release PGresult here to avoid memory leak.
+ 		 */
+ 		store_result(node, res);
+ 		PQclear(res);
+ 		res = NULL;
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		PQclear(res);
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ }
+ 
+ /*
+  * Fetch next partial result from remote server.
+  */
+ static PGresult *
+ fetch_result(ForeignScanState *node)
+ {
+ 	PgsqlFdwExecutionState *festate;
+ 	List		   *fdw_private;
+ 	char		   *sql;
+ 	PGconn		   *conn;
+ 	PGresult	   *res;
+ 
+ 	festate = (PgsqlFdwExecutionState *) node->fdw_state;
+ 
+ 	/* retrieve information for fetching result. */
+ 	fdw_private = festate->fdwplan->fdw_private;
+ 	sql = strVal(list_nth(fdw_private, FdwPrivateFetchSql));
+ 	conn = festate->conn;
+ 	res = PQexec(conn, sql);
+ 	if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK)
+ 	{
+ 		ereport(ERROR,
+ 				(errmsg("could not fetch rows from foreign server"),
+ 				 errdetail("%s", PQerrorMessage(conn)),
+ 				 errhint("%s", sql)));
+ 	}
+ 
+ 	return res;
+ }
+ 
+ /*
+  * Create tuples from PGresult and store them into tuplestore.
+  */
+ static void
+ store_result(ForeignScanState *node, PGresult *res)
+ {
+ 	int					rows;
+ 	int					row;
+ 	int					i;
+ 	int					nfields;
+ 	int					attnum;		/* number of non-dropped columns */
+ 	Form_pg_attribute  *attrs;
+ 	TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+ 	TupleDesc	tupdesc = slot->tts_tupleDescriptor;
+ 	PgsqlFdwExecutionState *festate;
+ 
+ 	festate = (PgsqlFdwExecutionState *) node->fdw_state;
+ 	rows = PQntuples(res);
+ 	nfields = PQnfields(res);
+ 	attrs = tupdesc->attrs;
+ 
+ 	/* First, ensure that the tuplestore is empty. */
+ 	if (festate->tuples == NULL)
+ 	{
+ 		MemoryContext	oldcontext = CurrentMemoryContext;
+ 
+ 		/*
+ 		 * Create tuplestore to store result of the query in per-query context.
+ 		 * Note that we use this memory context to avoid memory leak in error
+ 		 * cases.
+ 		 */
+ 		MemoryContextSwitchTo(MessageContext);
+ 		festate->tuples = tuplestore_begin_heap(false, false, work_mem);
+ 		MemoryContextSwitchTo(oldcontext);
+ 	}
+ 	else
+ 	{
+ 		/* We already have tuplestore, just need to clear contents of it. */
+ 		tuplestore_clear(festate->tuples);
+ 	}
+ 	
+ 
+ 	/* count non-dropped columns */
+ 	for (attnum = 0, i = 0; i < tupdesc->natts; i++)
+ 		if (!attrs[i]->attisdropped)
+ 			attnum++;
+ 
+ 	/* check result and tuple descriptor have the same number of columns */
+ 	if (attnum > 0 && attnum != nfields)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("remote query result rowtype does not match "
+ 						"the specified FROM clause rowtype"),
+ 				 errdetail("expected %d, actual %d", attnum, nfields)));
+ 
+ 	/* put a tuples into the slot */
+ 	for (row = 0; row < rows; row++)
+ 	{
+ 		int			j;
+ 		HeapTuple	tuple;
+ 
+ 		for (i = 0, j = 0; i < tupdesc->natts; i++)
+ 		{
+ 			/* skip dropped columns. */
+ 			if (attrs[i]->attisdropped)
+ 			{
+ 				festate->col_values[i] = NULL;
+ 				continue;
+ 			}
+ 
+ 			if (PQgetisnull(res, row, j))
+ 				festate->col_values[i] = NULL;
+ 			else
+ 				festate->col_values[i] = PQgetvalue(res, row, j);
+ 			j++;
+ 		}
+ 
+ 		/*
+ 		 * Build the tuple and put it into the slot.
+ 		 * We don't have to free the tuple explicitly because it's been
+ 		 * allocated in the per-tuple context.
+ 		 */
+ 		tuple = BuildTupleFromCStrings(festate->attinmeta, festate->col_values);
+ 		tuplestore_puttuple(festate->tuples, tuple);
+ 	}
+ 
+ 	tuplestore_donestoring(festate->tuples);
+ }
diff --git a/contrib/pgsql_fdw/pgsql_fdw.control b/contrib/pgsql_fdw/pgsql_fdw.control
index ...0a9c8f4 .
*** a/contrib/pgsql_fdw/pgsql_fdw.control
--- b/contrib/pgsql_fdw/pgsql_fdw.control
***************
*** 0 ****
--- 1,5 ----
+ # pgsql_fdw extension
+ comment = 'foreign-data wrapper for remote PostgreSQL servers'
+ default_version = '1.0'
+ module_pathname = '$libdir/pgsql_fdw'
+ relocatable = true
diff --git a/contrib/pgsql_fdw/pgsql_fdw.h b/contrib/pgsql_fdw/pgsql_fdw.h
index ...d5e5d4b .
*** a/contrib/pgsql_fdw/pgsql_fdw.h
--- b/contrib/pgsql_fdw/pgsql_fdw.h
***************
*** 0 ****
--- 1,28 ----
+ /*-------------------------------------------------------------------------
+  *
+  * pgsql_fdw.h
+  *		  foreign-data wrapper for remote PostgreSQL servers.
+  *
+  * Copyright (c) 2011, PostgreSQL Global Development Group
+  *
+  * IDENTIFICATION
+  *		  pgsql_fdw/pgsql_fdw.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #ifndef PGSQL_FDW_H
+ #define PGSQL_FDW_H
+ 
+ #include "postgres.h"
+ #include "nodes/relation.h"
+ 
+ /* in option.c */
+ int ExtractConnectionOptions(List *defelems,
+ 							 const char **keywords,
+ 							 const char **values);
+ 
+ /* in deparse.c */
+ char *deparseSql(Oid relid, PlannerInfo *root, RelOptInfo *baserel);
+ 
+ #endif /* PGSQL_FDW_H */
diff --git a/contrib/pgsql_fdw/sql/pgsql_fdw.sql b/contrib/pgsql_fdw/sql/pgsql_fdw.sql
index ...b6b99ab .
*** a/contrib/pgsql_fdw/sql/pgsql_fdw.sql
--- b/contrib/pgsql_fdw/sql/pgsql_fdw.sql
***************
*** 0 ****
--- 1,175 ----
+ -- ===================================================================
+ -- create FDW objects
+ -- ===================================================================
+ 
+ CREATE EXTENSION pgsql_fdw;
+ 
+ CREATE SERVER loopback1 FOREIGN DATA WRAPPER pgsql_fdw;
+ CREATE SERVER loopback2 FOREIGN DATA WRAPPER pgsql_fdw
+   OPTIONS (dbname 'contrib_regression');
+ 
+ CREATE USER MAPPING FOR public SERVER loopback1
+ 	OPTIONS (user 'value', password 'value');
+ CREATE USER MAPPING FOR public SERVER loopback2;
+ 
+ CREATE FOREIGN TABLE ft1 (
+ 	c1 int NOT NULL,
+ 	c2 int NOT NULL,
+ 	c3 text,
+ 	c4 timestamptz,
+ 	c5 timestamp
+ ) SERVER loopback2;
+ 
+ CREATE FOREIGN TABLE ft2 (
+ 	c1 int NOT NULL,
+ 	c2 int NOT NULL,
+ 	c3 text,
+ 	c4 timestamptz,
+ 	c5 timestamp
+ ) SERVER loopback2;
+ 
+ -- ===================================================================
+ -- create objects used through FDW
+ -- ===================================================================
+ CREATE SCHEMA "S 1";
+ CREATE TABLE "S 1"."T 1" (
+ 	c1 int NOT NULL,
+ 	c2 int NOT NULL,
+ 	c3 text,
+ 	c4 timestamptz,
+ 	c5 timestamp,
+ 	CONSTRAINT t1_pkey PRIMARY KEY (c1)
+ );
+ CREATE TABLE "S 1"."T 2" (
+ 	c1 int NOT NULL,
+ 	c2 text,
+ 	CONSTRAINT t2_pkey PRIMARY KEY (c1)
+ );
+ 
+ BEGIN;
+ TRUNCATE "S 1"."T 1";
+ INSERT INTO "S 1"."T 1"
+ 	SELECT id,
+ 	       id % 10,
+ 	       to_char(id, 'FM00000'),
+ 	       '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval,
+ 	       '1970-01-01'::timestamp + ((id % 100) || ' days')::interval
+ 	FROM generate_series(1, 1000) id;
+ TRUNCATE "S 1"."T 2";
+ INSERT INTO "S 1"."T 2"
+ 	SELECT id,
+ 	       'AAA' || to_char(id, 'FM000')
+ 	FROM generate_series(1, 100) id;
+ COMMIT;
+ 
+ -- ===================================================================
+ -- tests for pgsql_fdw_validator
+ -- ===================================================================
+ ALTER FOREIGN DATA WRAPPER pgsql_fdw OPTIONS (host 'value');    -- ERRROR
+ -- requiressl, krbsrvname and gsslib are omitted because they depend on
+ -- configure option
+ ALTER SERVER loopback1 OPTIONS (
+ 	--authtype 'value',
+ 	service 'value',
+ 	connect_timeout 'value',
+ 	dbname 'value',
+ 	host 'value',
+ 	hostaddr 'value',
+ 	port 'value',
+ 	--client_encoding 'value',
+ 	--tty 'value',
+ 	options 'value',
+ 	application_name 'value',
+ 	--fallback_application_name 'value',
+ 	keepalives 'value',
+ 	keepalives_idle 'value',
+ 	keepalives_interval 'value',
+ 	-- requiressl 'value',
+ 	sslmode 'value',
+ 	sslcert 'value',
+ 	sslkey 'value',
+ 	sslrootcert 'value',
+ 	sslcrl 'value'
+ 	--requirepeer 'value',
+ 	-- krbsrvname 'value',
+ 	-- gsslib 'value',
+ 	--replication 'value'
+ );
+ ALTER SERVER loopback1 OPTIONS (user 'value');                  -- ERROR
+ ALTER SERVER loopback2 OPTIONS (ADD min_cursor_rows '0');
+ ALTER SERVER loopback2 OPTIONS (ADD fetch_count '2');
+ ALTER USER MAPPING FOR public SERVER loopback1
+ 	OPTIONS (DROP user, DROP password);
+ ALTER USER MAPPING FOR public SERVER loopback1
+ 	OPTIONS (host 'value');                                     -- ERROR
+ ALTER FOREIGN TABLE ft1 OPTIONS (nspname 'S 1', relname 'T 1');
+ ALTER FOREIGN TABLE ft2 OPTIONS (nspname 'S 1', relname 'T 1', min_cursor_rows '10', fetch_count '100');
+ ALTER FOREIGN TABLE ft1 OPTIONS (invalid 'value');              -- ERROR
+ ALTER FOREIGN TABLE ft1 OPTIONS (min_cursor_rows 'a');          -- ERROR
+ ALTER FOREIGN TABLE ft1 OPTIONS (min_cursor_rows '-1');         -- ERROR
+ ALTER FOREIGN TABLE ft1 OPTIONS (fetch_count 'a');              -- ERROR
+ ALTER FOREIGN TABLE ft1 OPTIONS (fetch_count '0');              -- ERROR
+ ALTER FOREIGN TABLE ft1 OPTIONS (fetch_count '-1');             -- ERROR
+ \dew+
+ \des+
+ \deu+
+ \det+
+ 
+ -- ===================================================================
+ -- simple queries
+ -- ===================================================================
+ -- single table, with/without alias
+ EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
+ SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
+ EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
+ SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
+ -- with WHERE clause
+ SELECT * FROM ft1 t1 WHERE t1.c1 = 101;
+ -- aggregate
+ SELECT COUNT(*) FROM ft1 t1;
+ -- join two tables
+ SELECT t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
+ -- subquery
+ SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1;
+ -- subquery+MAX
+ SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1;
+ -- used in CTE
+ WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1;
+ -- fixed values
+ SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1;
+ 
+ -- ===================================================================
+ -- parameterized queries
+ -- ===================================================================
+ -- simple join
+ PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
+ EXPLAIN (COSTS false) EXECUTE st1(1, 2);
+ EXECUTE st1(1, 1);
+ EXECUTE st1(101, 101);
+ -- subquery using stable function (can't be pushed down)
+ PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c4) = 6) ORDER BY c1;
+ EXPLAIN (COSTS false) EXECUTE st2(10, 20);
+ EXECUTE st2(10, 20);
+ EXECUTE st1(101, 101);
+ -- subquery using immutable function (can be pushed down)
+ PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c5) = 6) ORDER BY c1;
+ EXPLAIN (COSTS false) EXECUTE st3(10, 20);
+ EXECUTE st3(10, 20);
+ EXECUTE st3(20, 30);
+ -- cleanup
+ DEALLOCATE st1;
+ DEALLOCATE st2;
+ DEALLOCATE st3;
+ 
+ -- ===================================================================
+ -- connection management
+ -- ===================================================================
+ SELECT srvname, usename FROM pgsql_fdw_connections;
+ SELECT pgsql_fdw_disconnect(srvid, usesysid) FROM pgsql_fdw_get_connections();
+ SELECT srvname, usename FROM pgsql_fdw_connections;
+ 
+ -- ===================================================================
+ -- cleanup
+ -- ===================================================================
+ DROP EXTENSION pgsql_fdw CASCADE;
+ 
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index adf09ca..65c7e81 100644
*** a/doc/src/sgml/contrib.sgml
--- b/doc/src/sgml/contrib.sgml
*************** CREATE EXTENSION <replaceable>module_nam
*** 117,122 ****
--- 117,123 ----
   &pgcrypto;
   &pgfreespacemap;
   &pgrowlocks;
+  &pgsql-fdw;
   &pgstandby;
   &pgstatstatements;
   &pgstattuple;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index ed39e0b..d72590f 100644
*** a/doc/src/sgml/filelist.sgml
--- b/doc/src/sgml/filelist.sgml
***************
*** 123,128 ****
--- 123,129 ----
  <!ENTITY pgcrypto        SYSTEM "pgcrypto.sgml">
  <!ENTITY pgfreespacemap  SYSTEM "pgfreespacemap.sgml">
  <!ENTITY pgrowlocks      SYSTEM "pgrowlocks.sgml">
+ <!ENTITY pgsql-fdw       SYSTEM "pgsql-fdw.sgml">
  <!ENTITY pgstandby       SYSTEM "pgstandby.sgml">
  <!ENTITY pgstatstatements SYSTEM "pgstatstatements.sgml">
  <!ENTITY pgstattuple     SYSTEM "pgstattuple.sgml">
diff --git a/doc/src/sgml/pgsql-fdw.sgml b/doc/src/sgml/pgsql-fdw.sgml
index ...6b7cb94 .
*** a/doc/src/sgml/pgsql-fdw.sgml
--- b/doc/src/sgml/pgsql-fdw.sgml
***************
*** 0 ****
--- 1,273 ----
+ <!-- doc/src/sgml/pgsql-fdw.sgml -->
+ 
+ <sect1 id="pgsql-fdw" xreflabel="pgsql_fdw">
+  <title>pgsql_fdw</title>
+ 
+  <indexterm zone="pgsql-fdw">
+   <primary>pgsql_fdw</primary>
+  </indexterm>
+ 
+  <para>
+   The <filename>pgsql_fdw</filename> module provides a foreign-data wrapper for
+   external <productname>PostgreSQL</productname> servers.
+   With this module, users can access data stored in external
+   <productname>PostgreSQL</productname> via plain SQL statements.
+  </para>
+ 
+  <para>
+   The <application>pgsql_fdw</application> can be installed on only
+   <productname>PostgreSQL</productname> 9.1 or later, but it can also access
+   data stored in <productname>PostgreSQL</productname> 9.0.
+   <productname>PostgreSQL</productname>8.4 or older are not available as remote
+   server because <application>pgsql_fdw</application> uses YAML format of
+   <command>EXPLAIN</command> output.
+  </para>
+ 
+  <para>
+   Note that default wrapper <literal>pgsql_fdw</literal> is created
+   automatically during <command>CREATE EXTENSION</command> command for
+   <application>pgsql_fdw</application>.
+  </para>
+ 
+  <sect2>
+   <title>FDW Options of pgsql_fdw</title>
+ 
+   <sect3>
+    <title>Connection Options</title>
+    <para>
+     A foreign server and user mapping created using this wrapper can have
+     <application>libpq</> connection options, expect below:
+ 
+     <itemizedlist>
+      <listitem><para>authtype</para></listitem>
+      <listitem><para>client_encoding</para></listitem>
+      <listitem><para>tty</para></listitem>
+      <listitem><para>fallback_application_name</para></listitem>
+      <listitem><para>replication</para></listitem>
+     </itemizedlist>
+ 
+     For details of <application>libpq</> connection options, see
+     <xref linkend="libpq-connect">.
+    </para>
+ 
+    <para>
+     <literal>user</literal> and <literal>password</literal> can be
+     specified on user mappings, and others can be specified on foreign servers.
+    </para>
+   </sect3>
+ 
+   <sect3>
+    <title>Object Name Options</title>
+    <para>
+     Foreign tables which were created using this wrapper, or its columns can
+     have object name options.  These options can be used to specify the names
+     used in SQL statement sent to remote <productname>PostgreSQL</productname>
+     server.  These options are useful when a remote object has different name
+     from corresponding local one.
+    </para>
+ 
+    <variablelist>
+ 
+     <varlistentry>
+      <term><literal>nspname</literal></term>
+      <listitem>
+       <para>
+        This option, which can be specified on a foreign table, is used as a
+        namespace (schema) reference in the SQL statement.  If this options is
+        omitted, <literal>pg_class.nspname</literal> of the foreign table is
+        used.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
+     <varlistentry>
+      <term><literal>relname</literal></term>
+      <listitem>
+       <para>
+        This option, which can be specified on a foreign table, is used as a
+        relation (table) reference in the SQL statement.  If this options is
+        omitted, <literal>pg_class.relname</literal> of the foreign table is
+        used.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
+     <varlistentry>
+      <term><literal>colname</literal></term>
+      <listitem>
+       <para>
+        This option, which can be specified on a column of a foreign table, is
+        used as a column (attribute) reference in the SQL statement.  If this
+        options is omitted, <literal>pg_attribute.attname</literal> of the column
+        of the foreign table is used.
+       </para>
+      </listitem>
+     </varlistentry>
+  
+    </variablelist>
+ 
+   </sect3>
+ 
+   <sect3>
+    <title>Cursor Options</title>
+    <para>
+     The <application>pgsql_fdw</application> switches the way used to retrieve
+     result of remote query according to estimated number of result rows.
+     If the estimation was less than a threshold, the result rows are retrieved
+     at once with simple <command>SELECT</command> statement.
+     In contrast, if the estimation is larger than the threshold, the result
+     rows are fetched separately with a cursor defined by
+     <command>DECLARE</command> statement.
+    </para>
+ 
+    <para>
+     Users can control this behavior with setting cursor options for a foreign
+     table.
+    </para>
+ 
+    <variablelist>
+ 
+     <varlistentry>
+      <term><literal>min_cursor_rows</literal></term>
+      <listitem>
+       <para>
+        This option specifies the minimum estimated number of result rows to use
+        cursor for fetching result.  Setting this to 1 means that every query
+        uses cursor, and setting to 0 means that every query uses simple
+        <command>SELECT</command>.  This option can be set on a foreign table,
+        and accepts only positive integer value.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
+     <varlistentry>
+      <term><literal>fetch_count</literal></term>
+      <listitem>
+       <para>
+        This option specifies the number of rows fetched at once in cursor mode.
+        This option accepts only integer value larger than zero.
+       </para>
+      </listitem>
+     </varlistentry>
+ 
+    </variablelist>
+ 
+   </sect3>
+ 
+  </sect2>
+ 
+  <sect2>
+   <title>Connection Management</title>
+ 
+   <para>
+    The <application>pgsql_fdw</application> establishes a connection to a
+    foreign server in the beginning of the first query which uses a foreign
+    table associated to the foreign server, and reuses the connection following
+    queries and even in following foreign scans in same query.
+ 
+    You can see the list of active connections via
+    <structname>pgsql_fdw_connections</structname> view.  It shows pair of oid
+    and name of server and local role for each active connections established by
+    <application>pgsql_fdw</application>.  For security reason, only superuser
+    can see other role's connections.
+   </para>
+ 
+   <para>
+    Established connections are kept alive until local role changes or the
+    current transaction aborts or user requests so.
+   </para>
+ 
+   <para>
+    If role has been changed, active connections established as old local role
+    is kept alive but never be reused until locla role has restored to original
+    role.  This kind of situation happens with <command>SET ROLE</command> and
+    <command>SET SESSION AUTHORIZATION</command>.
+   </para>
+ 
+   <para>
+    If current transaction aborts by error or user request, all active
+    connections are disconnected automatically.  This behavior avoids possible
+    connection leaks on error.
+   </para>
+ 
+   <para>
+    You can discard persistent connection at arbitrary timing with
+    <function>pgsql_fdw_disconnect()</function>.  It takes server oid and
+    user oid as arguments.  This function can handle only connections
+    established in current session; connections established by other backends
+    are not reachable.
+   </para>
+ 
+   <para>
+    You can discard all active and visible connections in current session with
+    using <structname>pgsql_fdw_connections</structname> and
+    <function>pgsql_fdw_disconnect()</function> together:
+ <synopsis>
+ postgres=# SELECT pgsql_fdw_disconnect(srvid, usesysid) FROM pgsql_fdw_connections;
+  pgsql_fdw_disconnect
+ ----------------------
+  OK
+  OK
+ (2 rows)
+ </synopsis>
+   </para>
+  </sect2>
+ 
+  <sect2>
+   <title>Transaction Management</title>
+   <para>
+    The <application>pgsql_fdw</application> executes <command>BEGIN</command>
+    command when a new connection has established.  This means that all remote
+    queries for a foreign server are executed in a transaction.  Since the
+    default transaction isolation level is <literal>READ COMMITTED</literal>,
+    multiple foreign scans in a local query might produce inconsistent results.
+ 
+    To avoid this inconsistency, you can use <literal>SERIALIZABLE</literal>
+    level for remote transaction with setting
+    <literal>default_transaction_isolation</literal> for the user used for
+    <application>pgsql_fdw</application> connection on remote side.
+   </para>
+  </sect2>
+ 
+  <sect2>
+   <title>Estimation of Costs and Rows</title>
+   <para>
+    The <application>pgsql_fdw</application> estimates the costs of a foreign
+    scan by adding some basic costs:  connection costs, remote query costs and
+    data transfer costs.
+    To get remote query costs <application>pgsql_fdw</application> executes
+    <command>EXPLAIN</command> command on remote server for each foreign scan.
+   </para>
+ 
+   <para>
+    On the other hand, estimated rows which was returned by
+    <command>EXPLAIN</command> is used for local estimation as-is.
+   </para>
+  </sect2>
+ 
+  <sect2>
+   <title>EXPLAIN Output</title>
+   <para>
+    For a foreign table using <literal>pgsql_fdw</>, <command>EXPLAIN</> shows
+    a remote SQL statement which is sent to remote
+    <productname>PostgreSQL</productname> server for a ForeignScan plan node.
+     For example:
+   </para>
+ <synopsis>
+ postgres=# EXPLAIN SELECT aid FROM pgbench_accounts WHERE abalance < 0;
+                                           QUERY PLAN
+ -----------------------------------------------------------------------------------------------
+  Foreign Scan on pgbench_accounts  (cost=100.00..8769.00 rows=1 width=4)
+    Remote SQL: SELECT aid, NULL, NULL, NULL FROM public.pgbench_accounts WHERE (abalance < 0)
+ (2 rows)
+ </synopsis>
+  </sect2>
+ 
+  <sect2>
+   <title>Author</title>
+   <para>
+    Shigeru Hanada <email>shigeru.hanada@gmail.com</email>
+   </para>
+  </sect2>
+ 
+ </sect1>
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 75923a6..6fda053 100644
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** deparse_context_for(const char *aliasnam
*** 2161,2166 ****
--- 2161,2189 ----
  	return list_make1(dpns);
  }
  
+ /* ----------
+  * deparse_context_for_rtelist	- Build deparse context for a single relation
+  *
+  * Given the list of RangeTableEnetry, build deparsing context for an
+  * expression referencing those relations.	This is sufficient for uses of
+  * deparse_expression before plan has been created.
+  * ----------
+  */
+ List *
+ deparse_context_for_rtelist(List *rtable)
+ {
+ 	deparse_namespace *dpns;
+ 
+ 	dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace));
+ 
+ 	/* Build a minimal RTE for the rel */
+ 	dpns->rtable = rtable;
+ 	dpns->ctes = NIL;
+ 
+ 	/* Return a one-deep namespace stack */
+ 	return list_make1(dpns);
+ }
+ 
  /*
   * deparse_context_for_planstate	- Build deparse context for a plan
   *
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 8a1c82e..a56366f 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern char *deparse_expression(Node *ex
*** 626,631 ****
--- 626,632 ----
  extern List *deparse_context_for(const char *aliasname, Oid relid);
  extern List *deparse_context_for_planstate(Node *planstate, List *ancestors,
  							  List *rtable);
+ extern List *deparse_context_for_rtelist(List *rtable);
  extern const char *quote_identifier(const char *ident);
  extern char *quote_qualified_identifier(const char *qualifier,
  						   const char *ident);
fdw_helper_doc.patchtext/plain; name=fdw_helper_doc.patchDownload
commit 5a2f1314d197c037dfd55ea1dc0395f46333c05d
Author: Shigeru Hanada <shigeru.hanada@gmail.com>
Date:   Fri Oct 21 11:54:39 2011 +0900

    Add document about helper functions for FDW authors.

diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 76ff243..db02d13 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -235,4 +235,98 @@ EndForeignScan (ForeignScanState *node);
 
    </sect1>
 
+   <sect1 id="fdw-helpers">
+    <title>Foreign Data Wrapper Helper Functions</title>
+
+    <para>
+     Several helper functions are exported from core so that authors of FDW
+     can get easy access to attributes of FDW-related objects such as FDW
+     options.
+    </para>
+
+    <para>
+<programlisting>
+ForeignDataWrapper *
+GetForeignDataWrapper(Oid fdwid);
+</programlisting>
+
+     This function returns a <structname>ForeignDataWrapper</structname> object
+     for a foreign-data wrapper with given oid.  A
+     <structname>ForeignDataWrapper</structname> object contains oid of the
+     wrapper itself, oid of the owner of the wrapper, name of the wrapper, oid
+     of fdwhandler function, oid of fdwvalidator function, and FDW options in
+     the form of list of <structname>DefElem</structname>.
+    </para>
+
+    <para>
+<programlisting>
+ForeignServer *
+GetForeignServer(Oid serverid);
+</programlisting>
+
+     This function returns a <structname>ForeignServer</structname> object for
+     a foreign server with given oid.  A <structname>ForeignServer</structname>
+     object contains oid of the server, oid of the wrapper for the server, oid
+     of the owner of the server, name of the server, type of the server,
+     version of the server, and FDW options in the form of list of
+     <structname>DefElem</structname>.
+    </para>
+
+    <para>
+<programlisting>
+UserMapping *
+GetUserMapping(Oid userid, Oid serverid);
+</programlisting>
+
+     This function returns a <structname>UserMapping</structname> object for a
+     user mapping with given oid pair.  A <structname>UserMapping</structname>
+     object contains oid of the user, oid of the server, and FDW options in the
+     form of list of <structname>DefElem</structname>.
+    </para>
+
+    <para>
+<programlisting>
+ForeignTable *
+GetForeignTable(Oid relid);
+</programlisting>
+
+     This function returns a <structname>ForeignTable</structname> object for a
+     foreign table with given oid.  A <structname>ForeignTable</structname>
+     object contains oid of the foreign table, oid of the server for the table,
+     and FDW options in the form of list of <structname>DefElem</structname>.
+    </para>
+
+    <para>
+     Some object types have name-based functions.
+    </para>
+
+    <para>
+<programlisting>
+ForeignDataWrapper *
+GetForeignDataWrapperByName(const char *name, bool missing_ok);
+</programlisting>
+
+     This function returns a <structname>ForeignDataWrapper</structname> object
+     for a foreign-data wrapper with given name.  If the wrapper is not found,
+     return NULL if missing_ok was true, otherwise raise an error.
+    </para>
+
+    <para>
+<programlisting>
+ForeignServer *
+GetForeignServerByName(const char *name, bool missing_ok);
+</programlisting>
+
+     This function returns a <structname>ForeignServer</structname> object for
+     a foreign server with given name.  If the server is not found, return NULL
+     if missing_ok was true, otherwise raise an error.
+    </para>
+
+    <para>
+     To use any of these functions, you need to include
+     <filename>foreign/foreign.h</filename> in your source file.
+    </para>
+
+  </sect1>
+
  </chapter>
#15Shigeru Hanada
shigeru.hanada@gmail.com
In reply to: Shigeru Hanada (#14)
1 attachment(s)
Re: WIP: Join push-down for foreign tables

(2011/10/24 20:40), I wrote:

I've found some issues for this CF item, and I would need some more
effort to solve them. So I'll mark this item as "Returned with
feedback", and I'll propose this idea again for next CF (2011-11).

This is the second effort for $SUBJECT. Attached patch requires
pgsql_fdw patches[1]http://archives.postgresql.org/pgsql-hackers/2011-11/msg00904.php to be applied previously. This patch provides:

* Changes for backend
* Add new planner node ForeignJoinPath and related routines. In
current design, planner consider all of possible join combinations
between foreign tables, similar to local joins such as nested loop,
hash join and merge join. And if foreign join is cheapest, planner
produces a ForeignScan plan node for a join. So executor is not
modified heavily since 9.1.
* Add new FDW callback for planning join push-down between foreign
tables on same server. This function is optional, and allowed to
return NULL to tell planner that that join can't be handled by the
FDW.
* Add server oid to RelOptInfo. This is useless at all for relations
other than foreign scan and foreign join, but it would reduces
catalog lookup during foreign scan/join planning.
* Add enable_foreignjoin GUC parameter. Join between foreign tables
is considered only when this parameter is on.
* Changes for pgsql_fdw
* Implemente PlanForeignJoin callback function.

[1]: http://archives.postgresql.org/pgsql-hackers/2011-11/msg00904.php

Regards,
--
Shigeru Hanada

Attachments:

join_pushdown.patchtext/plain; name=join_pushdown.patchDownload
diff --git a/contrib/pgsql_fdw/deparse.c b/contrib/pgsql_fdw/deparse.c
index b5f6c79..d24b629 100644
*** a/contrib/pgsql_fdw/deparse.c
--- b/contrib/pgsql_fdw/deparse.c
*************** is_proc_remotely_executable(Oid procid)
*** 362,364 ****
--- 362,538 ----
  	return true;
  }
  
+ /*
+  * Append WHERE clause elements to buf.
+  */
+ static void
+ append_where_clause(StringInfo buf, List *context, Path *path, bool *first)
+ {
+ 	List		   *restrictinfo;
+ 	ListCell	   *lc;
+ 	Path		   *outer_path = NULL;
+ 	Path		   *inner_path = NULL;
+ 
+ 	/* Determine that which restrictinfo is used. */
+ 	if (IsA(path, ForeignPath))
+ 	{
+ 		restrictinfo = path->parent->baserestrictinfo;
+ 	}
+ 	else if (IsA(path, ForeignJoinPath))
+ 	{
+ 		JoinPath	   *joinpath = (JoinPath *) path;
+ 		restrictinfo = joinpath->joinrestrictinfo;
+ 		outer_path = joinpath->outerjoinpath;
+ 		inner_path = joinpath->innerjoinpath;
+ 	}
+ 
+ 	/* Deparse restrict expressions. */
+ 	foreach(lc, restrictinfo)
+ 	{
+ 		RestrictInfo   *ri  = (RestrictInfo *) lfirst(lc);
+ 
+ 		if (!*first)
+ 			appendStringInfo(buf, " AND ");
+ 		appendStringInfo(buf, "%s",
+ 			deparse_expression((Node *) ri->clause, context, true, false));
+ 		*first = false;
+ 	}
+ 
+ 	/* Use children's restrictinfo recursively, if any. */
+ 	if (outer_path != NULL)
+ 		append_where_clause(buf, context, outer_path, first);
+ 	if (inner_path != NULL)
+ 		append_where_clause(buf, context, inner_path, first);
+ }
+ 
+ /*
+  * Store qualified relation name into result.
+  */
+ static void
+ get_qualified_relname(Oid relid, StringInfo result)
+ {
+ 	ForeignTable   *ft;				/* foreign table to be scanned */
+ 	const char *nspname = NULL;		/* plain namespace name */
+ 	const char *relname = NULL;		/* plain relation name */
+ 	const char *q_nspname;			/* quoted namespace name */
+ 	const char *q_relname;			/* quoted relation name */
+ 
+ 	ft = GetForeignTable(relid);
+ 	if (ft->options != NIL)
+ 	{
+ 		ListCell	   *lc;
+ 
+ 		foreach (lc, ft->options)
+ 		{
+ 			DefElem	   *opt = lfirst(lc);
+ 			if (strcmp(opt->defname, "nspname") == 0)
+ 				nspname = strVal(opt->arg);
+ 			else if (strcmp(opt->defname, "relname") == 0)
+ 				relname = strVal(opt->arg);
+ 		}
+ 	}
+ 	if (nspname == NULL)
+ 		nspname = get_namespace_name(get_rel_namespace(relid));
+ 	if (relname == NULL)
+ 		relname = get_rel_name(relid);
+ 	q_nspname = quote_identifier(nspname);
+ 	q_relname = quote_identifier(relname);
+ 	appendStringInfo(result, "%s.%s", q_nspname, q_relname);
+ }
+ 
+ /*
+  * Deparse join representation into SQL statement which suits for remote
+  * PostgreSQL server.  See also deparseSql().
+  */
+ char *
+ deparseJoinSql(Oid serverid,
+ 			   PlannerInfo *root,
+ 			   RelOptInfo *joinrel,
+ 			   JoinType jointype,
+ 			   SpecialJoinInfo *sjinfo,
+ 			   Path *outer_path,
+ 			   Path *inner_path,
+ 			   List *restrict_clauses,
+ 			   List *pathkeys)
+ {
+ 	int				i;
+ 	List		   *rtables = NIL;
+ 	List		   *context;
+ 	StringInfoData	sql;
+ 	bool			first;
+ 	ListCell	   *lc;
+ 	Bitmapset	   *bms;
+ 
+ 	/*
+ 	 * First of all, check that this join can be pushed down.
+ 	 *
+ 	 * There are some redundancy such as duplicated attribute loop, but this
+ 	 * is necessary to avoid memory leak.
+ 	 */
+ 	foreach(lc, joinrel->reltargetlist)
+ 	{
+ 		Var	   *var = (Var *) lfirst(lc);
+ 		if (var->varattno < 0)
+ 			return NULL;
+ 	}
+ 
+ 	/* Create context used for deparsing this join. */
+ 	for (i = 1; i < root->simple_rel_array_size; i++)
+ 	{
+ 		rtables = lappend(rtables, copyObject(root->simple_rte_array[i]));
+ 	}
+ 	context = deparse_context_for_rtelist(rtables);
+ 
+ 	/* Initialize buffer. */
+ 	initStringInfo(&sql);
+ 
+ 	/* deparse SELECT clause */
+ 	/* TODO: replace unused references with NULL */
+ 	appendStringInfo(&sql, "SELECT ");
+ 	first = true;
+ 	foreach(lc, joinrel->reltargetlist)
+ 	{
+ 		Var	   *var = (Var *) lfirst(lc);
+ 		appendStringInfo(&sql, "%s%s", first ? "" : ", ",
+ 			deparse_expression((Node *) var, context, true, false));
+ 		first = false;
+ 	}
+ 
+ 	/* deparse FROM clause */
+ 	appendStringInfo(&sql, "\nFROM ");
+ 	bms = bms_copy(joinrel->relids);
+ 	first = true;
+ 	while (!bms_is_empty(bms))
+ 	{
+ 		int relid = bms_first_member(bms);
+ 		RangeTblEntry	   *rte = root->simple_rte_array[relid];
+ 
+ 		if (!first)
+ 			appendStringInfo(&sql, ", ");
+ 		get_qualified_relname(rte->relid, &sql);
+ 		appendStringInfo(&sql, " %s", rte->eref->aliasname);
+ 		first = false;
+ 	}
+ 
+ 	/* deparse WHERE clause */
+ 	appendStringInfo(&sql, "\nWHERE ");
+ 	first = true;
+ 	foreach(lc, restrict_clauses)
+ 	{
+ 		RestrictInfo   *ri  = (RestrictInfo *) lfirst(lc);
+ 
+ 		if (!first)
+ 			appendStringInfo(&sql, " AND ");
+ 		appendStringInfo(&sql, "%s",
+ 			deparse_expression((Node *) ri->clause, context, true, false));
+ 		first = false;
+ 	}
+ 	if (outer_path != NULL)
+ 		append_where_clause(&sql, context, outer_path, &first);
+ 	if (inner_path != NULL)
+ 		append_where_clause(&sql, context, inner_path, &first);
+ 
+ 	/* return constructed SQL statement. */
+ 	elog(DEBUG1, "deparsed join SQL: [%s]", sql.data);
+ 	return sql.data;
+ }
diff --git a/contrib/pgsql_fdw/pgsql_fdw.c b/contrib/pgsql_fdw/pgsql_fdw.c
index 61d20ee..564f45d 100644
*** a/contrib/pgsql_fdw/pgsql_fdw.c
--- b/contrib/pgsql_fdw/pgsql_fdw.c
*************** static void pgsqlBeginForeignScan(Foreig
*** 109,114 ****
--- 109,123 ----
  static TupleTableSlot *pgsqlIterateForeignScan(ForeignScanState *node);
  static void pgsqlReScanForeignScan(ForeignScanState *node);
  static void pgsqlEndForeignScan(ForeignScanState *node);
+ static FdwPlan *pgsqlPlanForeignJoin(Oid serverid,
+ 									 PlannerInfo *root,
+ 									 RelOptInfo *joinrel,
+ 									 JoinType jointype,
+ 									 SpecialJoinInfo *sjinfo,
+ 									 Path *outer_path,
+ 									 Path *inner_path,
+ 									 List *restrict_clauses,
+ 									 List *pathkeys);
  
  /*
   * Helper functions
*************** pgsql_fdw_handler(PG_FUNCTION_ARGS)
*** 141,188 ****
  	fdwroutine->ReScanForeignScan = pgsqlReScanForeignScan;
  	fdwroutine->EndForeignScan = pgsqlEndForeignScan;
  
  	PG_RETURN_POINTER(fdwroutine);
  }
  
  /*
!  * pgsqlPlanForeignScan
!  *		Create a FdwPlan for a scan on the foreign table
   */
! static FdwPlan *
! pgsqlPlanForeignScan(Oid foreigntableid,
! 					PlannerInfo *root,
! 					RelOptInfo *baserel)
  {
  	char			name[128];	/* must be larger than format + 10 */
  	StringInfoData	cursor;
  	const char	   *fetch_count_str;
  	int				fetch_count = DEFAULT_FETCH_COUNT;
- 	char		   *sql;
- 	FdwPlan		   *fdwplan;
  	List		   *fdw_private = NIL;
- 	ForeignTable   *table;
- 	ForeignServer  *server;
- 
- 	/* Construct FdwPlan with cost estimates */
- 	fdwplan = makeNode(FdwPlan);
- 	sql = deparseSql(foreigntableid, root, baserel);
- 	table = GetForeignTable(foreigntableid);
- 	server = GetForeignServer(table->serverid);
- 	estimate_costs(root, baserel, sql, server->serverid,
- 				   &fdwplan->startup_cost, &fdwplan->total_cost);
  
  	/*
  	 * Store plain SELECT statement in private area of FdwPlan.  This will be
  	 * used for executing remote query and explaining scan.
  	 */
! 	fdw_private = list_make1(makeString(sql));
  
  	/* Use specified fetch_count instead of default value, if any. */
! 	fetch_count_str = GetFdwOptionValue(InvalidOid, InvalidOid, foreigntableid,
  										InvalidAttrNumber, "fetch_count");
  	if (fetch_count_str != NULL)
  		fetch_count = strtol(fetch_count_str, NULL, 10);
! 	elog(DEBUG1, "relid=%u fetch_count=%d", foreigntableid, fetch_count);
  
  	/*
  	 * We store some more information in FdwPlan to pass them beyond the
--- 150,187 ----
  	fdwroutine->ReScanForeignScan = pgsqlReScanForeignScan;
  	fdwroutine->EndForeignScan = pgsqlEndForeignScan;
  
+ 	/* Optional handlers. */
+ 	fdwroutine->PlanForeignJoin = pgsqlPlanForeignJoin;
+ 
  	PG_RETURN_POINTER(fdwroutine);
  }
  
  /*
!  * Make list of private information which are specific to pgsql_fdw.
!  * Actual contents are:
!  *
   */
! static List *
! make_fdw_private(const char *sql, Oid serverid, Oid relid)
  {
  	char			name[128];	/* must be larger than format + 10 */
  	StringInfoData	cursor;
  	const char	   *fetch_count_str;
  	int				fetch_count = DEFAULT_FETCH_COUNT;
  	List		   *fdw_private = NIL;
  
  	/*
  	 * Store plain SELECT statement in private area of FdwPlan.  This will be
  	 * used for executing remote query and explaining scan.
  	 */
! 	fdw_private = list_make1(makeString(pstrdup(sql)));
  
  	/* Use specified fetch_count instead of default value, if any. */
! 	fetch_count_str = GetFdwOptionValue(InvalidOid, serverid, relid,
  										InvalidAttrNumber, "fetch_count");
  	if (fetch_count_str != NULL)
  		fetch_count = strtol(fetch_count_str, NULL, 10);
! 	elog(DEBUG1, "relid=%u fetch_count=%d", relid, fetch_count);
  
  	/*
  	 * We store some more information in FdwPlan to pass them beyond the
*************** pgsqlPlanForeignScan(Oid foreigntableid,
*** 224,231 ****
  	appendStringInfo(&cursor, "CLOSE %s", name);
  	fdw_private = lappend(fdw_private, makeString(cursor.data));
  
  	/* Store FDW private information into FdwPlan */
! 	fdwplan->fdw_private = fdw_private;
  
  	return fdwplan;
  }
--- 223,256 ----
  	appendStringInfo(&cursor, "CLOSE %s", name);
  	fdw_private = lappend(fdw_private, makeString(cursor.data));
  
+ 	return fdw_private;
+ }
+ 
+ /*
+  * pgsqlPlanForeignScan
+  *		Create a FdwPlan for a scan on the foreign table
+  */
+ static FdwPlan *
+ pgsqlPlanForeignScan(Oid foreigntableid,
+ 					PlannerInfo *root,
+ 					RelOptInfo *baserel)
+ {
+ 	char		   *sql;
+ 	FdwPlan		   *fdwplan;
+ 	ForeignTable   *table;
+ 	ForeignServer  *server;
+ 
+ 	/* Construct FdwPlan with cost estimates */
+ 	fdwplan = makeNode(FdwPlan);
+ 	sql = deparseSql(foreigntableid, root, baserel);
+ 	table = GetForeignTable(foreigntableid);
+ 	server = GetForeignServer(table->serverid);
+ 	estimate_costs(root, baserel, sql, server->serverid,
+ 				   &fdwplan->startup_cost, &fdwplan->total_cost);
+ 
  	/* Store FDW private information into FdwPlan */
! 	fdwplan->fdw_private = make_fdw_private(sql, table->serverid,
! 											foreigntableid);
  
  	return fdwplan;
  }
*************** pgsqlBeginForeignScan(ForeignScanState *
*** 254,261 ****
  {
  	PgsqlFdwExecutionState *festate;
  	PGconn		   *conn;
- 	Oid				relid;
- 	ForeignTable   *table;
  	ForeignServer  *server;
  	UserMapping	   *user;
  	TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
--- 279,284 ----
*************** pgsqlBeginForeignScan(ForeignScanState *
*** 276,284 ****
  	 * Get connection to the foreign server.  Connection manager would
  	 * establish new connection if necessary.
  	 */
! 	relid = RelationGetRelid(node->ss.ss_currentRelation);
! 	table = GetForeignTable(relid);
! 	server = GetForeignServer(table->serverid);
  	user = GetUserMapping(GetOuterUserId(), server->serverid);
  	conn = GetConnection(server, user);
  	festate->conn = conn;
--- 299,305 ----
  	 * Get connection to the foreign server.  Connection manager would
  	 * establish new connection if necessary.
  	 */
! 	server = GetForeignServer(node->serverid);
  	user = GetUserMapping(GetOuterUserId(), server->serverid);
  	conn = GetConnection(server, user);
  	festate->conn = conn;
*************** pgsqlEndForeignScan(ForeignScanState *no
*** 501,506 ****
--- 522,577 ----
  }
  
  /*
+  * pgsqlPlanForeignJoin
+  *		Create a FdwPlan for a query which contains of join of foreign tables
+  */
+ static FdwPlan *
+ pgsqlPlanForeignJoin(Oid serverid,
+ 					 PlannerInfo *root,
+ 					 RelOptInfo *joinrel,
+ 					 JoinType jointype,
+ 					 SpecialJoinInfo *sjinfo,
+ 					 Path *outer_path,
+ 					 Path *inner_path,
+ 					 List *restrict_clauses,
+ 					 List *pathkeys)
+ {
+ 	FdwPlan	   *fdwplan = NULL;
+ 	char	   *sql;
+ 
+ 	Assert(joinrel->serverid != InvalidOid);
+ 	Assert(IsA(outer_path, ForeignPath) || IsA(outer_path, ForeignJoinPath));
+ 	Assert(IsA(inner_path, ForeignPath) || IsA(inner_path, ForeignJoinPath));
+ 
+ 	/*
+ 	 * In this first version, consider only inner join, because OUTER JOIN
+ 	 * might populate more result rows than summary of result of children.
+ 	 */
+ 	if (jointype != JOIN_INNER)
+ 		return NULL;
+ 
+ 	/*
+ 	 * Deparse join represention into SQL statement which retrieves whole
+ 	 * result of the join.  NULL means that pgsql_fdw can't push this join down
+ 	 * to remote side.
+ 	 */
+ 	sql = deparseJoinSql(serverid, root, joinrel, jointype, sjinfo,
+ 						 outer_path, inner_path, restrict_clauses, pathkeys);
+ 	if (sql == NULL)
+ 		return NULL;
+ 
+ 	fdwplan = makeNode(FdwPlan);
+ 	fdwplan->startup_cost = 0.0;
+ 	fdwplan->total_cost = 0.0;
+ 	/* TODO estimate cost by executing EXPLAIN on remote side. */
+ 	estimate_costs(root, joinrel, sql, serverid, &fdwplan->startup_cost,
+ 				   &fdwplan->total_cost);
+ 	fdwplan->fdw_private = make_fdw_private(sql, serverid, InvalidOid);
+ 
+ 	return fdwplan;
+ }
+ 
+ /*
   * Estimate costs of scanning a foreign table.
   */
  static void
*************** estimate_costs(PlannerInfo *root, RelOpt
*** 526,535 ****
--- 597,609 ----
  	 * tend to choose custom plan.
  	 *
  	 * See comments in plancache.c for details of custom plan.
+ 	 *
+ 	 * TODO check this in deparse*Sql(), and receive a flag.
  	 */
  	foreach(lc, baserel->baserestrictinfo)
  	{
  		RestrictInfo	   *rs = (RestrictInfo *) lfirst(lc);
+ 
  		if (contain_ext_param((Node *) rs->clause))
  		{
  			*startup_cost = CONNECTION_COSTS;
diff --git a/contrib/pgsql_fdw/pgsql_fdw.h b/contrib/pgsql_fdw/pgsql_fdw.h
index fb49ffb..e0c3ee2 100644
*** a/contrib/pgsql_fdw/pgsql_fdw.h
--- b/contrib/pgsql_fdw/pgsql_fdw.h
*************** int ExtractConnectionOptions(List *defel
*** 24,28 ****
--- 24,37 ----
  
  /* in deparse.c */
  char *deparseSql(Oid relid, PlannerInfo *root, RelOptInfo *baserel);
+ char *deparseJoinSql(Oid serverid,
+ 					 PlannerInfo *root,
+ 					 RelOptInfo *joinrel,
+ 					 JoinType jointype,
+ 					 SpecialJoinInfo *sjinfo,
+ 					 Path *outer_path,
+ 					 Path *inner_path,
+ 					 List *restrict_clauses,
+ 					 List *pathkeys);
  
  #endif /* PGSQL_FDW_H */
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index d1e628f..78c5f39 100644
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
*************** SET ENABLE_SEQSCAN TO OFF;
*** 2362,2367 ****
--- 2362,2380 ----
        </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/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index d809cac..7e3c5bd 100644
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
*************** EndForeignScan (ForeignScanState *node);
*** 228,233 ****
--- 228,273 ----
      </para>
  
      <para>
+      Callback functions above are required for every foreign data wrappers.
+      In contrast, callback functions below are optional, so you can set each of
+      them to NULL to tell the planner and/or executor that the FDW doesn't
+      support the feature.  Actually, in such case, you don't have to set NULL
+      explicitly because <function>makeNode</function> has already initialized
+      them to NULL.
+     </para>
+ 
+     <para>
+ <programlisting>
+ FdwPlan *
+ PlanForeignJoin (Oid serverid,
+                  PlannerInfo *root,
+                  RelOptInfo *joinrel,
+                  JoinType jointype,
+                  SpecialJoinInfo *sjinfo,
+                  Path *outer_path,
+                  Path *inner_path,
+                  List *restrict_clauses,
+                  List *pathkeys);
+ </programlisting>
+ 
+      Plan a join between two foreign scans performed on same server.  In this
+      context, a foreign scans can be a simple foreign table reference planned
+      by <function>PlanForeignScan</function>, or a result of a foreign join
+      planned by <function>PlanForeignJoin</function>.
+      Arguments <literal>outer_path</literal> and <literal>inner_path</literal>
+      provide details of each child nodes, and other arguments provide details
+      of the join itself.
+     </para>
+ 
+     <para>
+      This function is called during planning to determine whether foreign join
+      can be pushed down.  FDW can use private area of
+      <structname>FdwPlan</structname>, similarly to
+      <function>PlanForeignScan</function>, to store FDW-specific information
+      which are needed to get result of the join.
+     </para>
+ 
+     <para>
       The <structname>FdwRoutine</> and <structname>FdwPlan</> struct types
       are declared in <filename>src/include/foreign/fdwapi.h</>, which see
       for additional details.
*************** GetForeignTable(Oid relid);
*** 298,303 ****
--- 338,362 ----
  
      <para>
  <programlisting>
+ char *
+ GetFdwOptionValue(Oid relid, AttrNumber attnum, const char *optname);
+ </programlisting>
+ 
+      This function returns a copy (created in current memory context) of the
+      value of the given option for the given object (relation or its column).
+      If attnum is InvalidAttrNumber, pg_attribute is ignored.
+      If specified option is set in multiple object level, the one in the
+      finest-grained object is used; e.g. priority is given to user mapping 
+      over than a foreign server for the mapping or foreign-data wrapper for the
+      server.
+      This function would be useful when you know which option is needed but you
+      don't know which object(s) have it.
+      If you already know the source object, it would be more efficient to use
+      object retrieval functions.
+     </para>
+ 
+     <para>
+ <programlisting>
  List *
  GetForeignTableColumnOptions(Oid relid, AttrNumber attnum);
  </programlisting>
*************** GetForeignServerByName(const char *name,
*** 334,388 ****
      </para>
  
      <para>
- <programlisting>
- char *
- GetFdwOptionValue(Oid fdwid, Oid serverid, Oid relid, AttrNumber attnum, const char *optname);
- </programlisting>
- 
-      This function returns a copied string (created in current memory context)
-      of the value of a FDW option with given name which is set on a object with
-      given oid and attribute number.  This function ignores catalogs if invalid
-      identifir is given for it.
- 
-      <itemizedlist>
-       <listitem>
-        <para>
-         If attnum is <literal>InvalidAttrNumber</literal> or relid is
-         <literal>Invalidoid</literal>, <structname>pg_attribute</structname> is
-         ignored.
-        </para>
-       </listitem>
-       <listitem>
-        <para>
-         If relid is <literal>InvalidOid</literal>,
-         <structname>pg_foreign_table</structname> is ignored.
-        </para>
-       </listitem>
-       <listitem>
-        <para>
-         If both serverid and relid are <literal>InvalidOid</literal>,
-         <structname>pg_foreign_server</structname> is ignored.
-        </para>
-       </listitem>
-       <listitem>
-        <para>
-         If all of fdwid, serverid and relid are <literal>InvalidOid</literal>,
-         <structname>pg_foreign_data_wrapper</structname> is ignored.
-        </para>
-       </listitem>
-      </itemizedlist>
-     </para>
- 
-     <para>
-      If the option with given name is set in multiple object level, the one in
-      the finest-grained object is used; e.g. priority is given to user mappings 
-      over than foreign servers.
-      This function would be useful when you know which option is needed but you
-      don't know where it is set.  If you already know the source object, it
-      would be more efficient to use object retrieval functions.
-     </para>
- 
-     <para>
       To use any of these functions, you need to include
       <filename>foreign/foreign.h</filename> in your source file.
      </para>
--- 393,398 ----
diff --git a/doc/src/sgml/ref/postgres-ref.sgml b/doc/src/sgml/ref/postgres-ref.sgml
index 9869a1f..d785c2f 100644
*** a/doc/src/sgml/ref/postgres-ref.sgml
--- b/doc/src/sgml/ref/postgres-ref.sgml
*************** PostgreSQL documentation
*** 376,382 ****
  
      <variablelist>
       <varlistentry>
!       <term><option>-f</option> <literal>{ s | i | o | b | t | n | m | h }</literal></term>
        <listitem>
         <para>
          Forbids the use of particular scan and join methods:
--- 376,382 ----
  
      <variablelist>
       <varlistentry>
!       <term><option>-f</option> <literal>{ s | i | o | b | t | n | m | h | f }</literal></term>
        <listitem>
         <para>
          Forbids the use of particular scan and join methods:
*************** PostgreSQL documentation
*** 385,392 ****
          <literal>o</literal>, <literal>b</literal> and <literal>t</literal>
          disable index-only scans, bitmap index scans, and TID scans
          respectively, while
!         <literal>n</literal>, <literal>m</literal>, and <literal>h</literal>
!         disable nested-loop, merge and hash joins respectively.
         </para>
  
         <para>
--- 385,393 ----
          <literal>o</literal>, <literal>b</literal> and <literal>t</literal>
          disable index-only scans, bitmap index scans, and TID scans
          respectively, while
!         <literal>n</literal>, <literal>m</literal>, <literal>h</literal>, and
!         <literal>f</literal> disable nested-loop, merge joins, hash joins, and
!         foreign joins respectively.
         </para>
  
         <para>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index e38de5c..86e0203 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static const char *explain_get_index_nam
*** 82,87 ****
--- 82,88 ----
  static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
  						ExplainState *es);
  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 *
*** 803,811 ****
  		case T_ValuesScan:
  		case T_CteScan:
  		case T_WorkTableScan:
- 		case T_ForeignScan:
  			ExplainScanTarget((Scan *) plan, es);
  			break;
  		case T_IndexScan:
  			{
  				IndexScan  *indexscan = (IndexScan *) plan;
--- 804,814 ----
  		case T_ValuesScan:
  		case T_CteScan:
  		case T_WorkTableScan:
  			ExplainScanTarget((Scan *) plan, es);
  			break;
+ 		case T_ForeignScan:
+ 			ExplainForeignScanTarget((Scan *) plan, es);
+ 			break;
  		case T_IndexScan:
  			{
  				IndexScan  *indexscan = (IndexScan *) plan;
*************** ExplainScanTarget(Scan *plan, ExplainSta
*** 1674,1679 ****
--- 1677,1702 ----
  }
  
  /*
+  * 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 65591e2..e7adb74 100644
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
*************** ExecAssignScanTypeFromOuterPlan(ScanStat
*** 760,765 ****
--- 760,790 ----
  	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..4bf6d6c 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
! 	{
! 		/* 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);
! 
! 		/*
! 		 * Initialize result tuple type and projection info.
! 		 */
! 		ExecAssignResultTypeFromTL(&scanstate->ss.ps);
! 		ExecAssignProjectionInfo(&scanstate->ss.ps, NULL);
! 	}
! 
! 	/* We know which server should be use. */
! 	scanstate->serverid = node->serverid;
  
  	/*
  	 * 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/foreign/foreign.c b/src/backend/foreign/foreign.c
index b984a5e..7c77847 100644
*** a/src/backend/foreign/foreign.c
--- b/src/backend/foreign/foreign.c
*************** GetFdwOptionValue(Oid fdwid, Oid serveri
*** 292,299 ****
  	ForeignDataWrapper  *wrapper = NULL;
  	char		   *value;
  
! 	/* Do we need to search pg_attribute? */
! 	if (attnum != InvalidAttrNumber && relid != InvalidOid)
  	{
  		value = get_options_value(GetForeignColumnOptions(relid, attnum),
  								  optname);
--- 292,299 ----
  	ForeignDataWrapper  *wrapper = NULL;
  	char		   *value;
  
! 	/* Do we need to use pg_attribute.attfdwoptions too? */
! 	if (attnum != InvalidAttrNumber)
  	{
  		value = get_options_value(GetForeignColumnOptions(relid, attnum),
  								  optname);
*************** GetFdwOptionValue(Oid fdwid, Oid serveri
*** 301,334 ****
  			return value;
  	}
  
- 	/* Do we need to search pg_foreign_table? */
  	if (relid != InvalidOid)
  	{
  		table = GetForeignTable(relid);
  		value = get_options_value(table->options, optname);
  		if (value != NULL)
  			return value;
- 
  		serverid = table->serverid;
  	}
  
- 	/* Do we need to search pg_user_mapping and pg_foreign_server? */
  	if (serverid != InvalidOid)
  	{
  		user = GetUserMapping(GetOuterUserId(), serverid);
  		value = get_options_value(user->options, optname);
  		if (value != NULL)
  			return value;
  
  		server = GetForeignServer(serverid);
  		value = get_options_value(server->options, optname);
  		if (value != NULL)
  			return value;
- 
  		fdwid = server->fdwid;
  	}
  
- 	/* Do we need to search pg_foreign_data_wrapper? */
  	if (fdwid != InvalidOid)
  	{
  		wrapper = GetForeignDataWrapper(fdwid);
--- 301,332 ----
  			return value;
  	}
  
  	if (relid != InvalidOid)
  	{
  		table = GetForeignTable(relid);
  		value = get_options_value(table->options, optname);
  		if (value != NULL)
  			return value;
  		serverid = table->serverid;
  	}
  
  	if (serverid != InvalidOid)
  	{
  		user = GetUserMapping(GetOuterUserId(), serverid);
  		value = get_options_value(user->options, optname);
  		if (value != NULL)
  			return value;
+ 	}
  
+ 	if (serverid != InvalidOid)
+ 	{
  		server = GetForeignServer(serverid);
  		value = get_options_value(server->options, optname);
  		if (value != NULL)
  			return value;
  		fdwid = server->fdwid;
  	}
  
  	if (fdwid != InvalidOid)
  	{
  		wrapper = GetForeignDataWrapper(fdwid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 63958c3..df3f25a 100644
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyForeignScan(ForeignScan *from)
*** 590,595 ****
--- 590,596 ----
  	/*
  	 * 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 f7d39ed..85f4071 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outForeignScan(StringInfo str, ForeignS
*** 557,562 ****
--- 557,563 ----
  
  	_outScanInfo(str, (Scan *) node);
  
+ 	WRITE_OID_FIELD(serverid);
  	WRITE_BOOL_FIELD(fsSystemCol);
  	WRITE_NODE_FIELD(fdwplan);
  }
*************** _outHashPath(StringInfo str, HashPath *n
*** 1661,1666 ****
--- 1662,1677 ----
  }
  
  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
*** 1749,1754 ****
--- 1760,1766 ----
  	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)
*** 2987,2992 ****
--- 2999,3007 ----
  			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 815b996..17b8cbe 100644
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** print_path(PlannerInfo *root, Path *path
*** 1610,1615 ****
--- 1610,1619 ----
  			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 348c36b..def47ee 100644
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
*************** bool		enable_nestloop = true;
*** 119,124 ****
--- 119,125 ----
  bool		enable_material = true;
  bool		enable_mergejoin = true;
  bool		enable_hashjoin = true;
+ bool		enable_foreignjoin = true;
  
  typedef struct
  {
*************** cost_mergejoin(MergePath *path, PlannerI
*** 2100,2105 ****
--- 2101,2111 ----
  }
  
  /*
+  * 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 8138b01..8a1f1c7 100644
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** static WorkTableScan *create_worktablesc
*** 75,80 ****
--- 75,82 ----
  						  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
*** 123,129 ****
  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,
--- 125,132 ----
  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
*** 221,226 ****
--- 224,230 ----
  		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
*** 385,390 ****
--- 389,401 ----
  													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
*** 1856,1861 ****
--- 1867,1873 ----
  	scan_plan = make_foreignscan(tlist,
  								 scan_clauses,
  								 scan_relid,
+ 								 rel->serverid,
  								 fsSystemCol,
  								 best_path->fdwplan);
  
*************** create_foreignscan_plan(PlannerInfo *roo
*** 1864,1869 ****
--- 1876,1926 ----
  	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;
+ 
+ 	/* 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 *
*** 3170,3175 ****
--- 3227,3233 ----
  make_foreignscan(List *qptlist,
  				 List *qpqual,
  				 Index scanrelid,
+ 				 Oid serverid,
  				 bool fsSystemCol,
  				 FdwPlan *fdwplan)
  {
*************** make_foreignscan(List *qptlist,
*** 3182,3187 ****
--- 3240,3246 ----
  	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 1e7aac9..ea5095b 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,
*** 1629,1631 ****
--- 1630,1712 ----
  
  	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 de629e9..d69be69 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 "catalog/heap.h"
  #include "miscadmin.h"
  #include "nodes/makefuncs.h"
*************** get_relation_info(PlannerInfo *root, Oid
*** 359,364 ****
--- 360,374 ----
  
  	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 37187e2..24cfe24 100644
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
*************** build_simple_rel(PlannerInfo *root, int 
*** 117,122 ****
--- 117,123 ----
  	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,
*** 371,376 ****
--- 372,378 ----
  	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,
*** 443,448 ****
--- 445,458 ----
  			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 976a832..e97a94d 100644
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
*************** set_plan_disabling_options(const char *a
*** 3093,3098 ****
--- 3093,3101 ----
  		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 da7b6d4..524582d 100644
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** static struct config_bool ConfigureNames
*** 765,770 ****
--- 765,779 ----
  		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 315db46..d1dec0e 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/execnodes.h b/src/include/nodes/execnodes.h
index 0a89f18..27d37a0 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct WorkTableScanState
*** 1463,1468 ****
--- 1463,1469 ----
  typedef struct ForeignScanState
  {
  	ScanState	ss;				/* its first field is NodeTag */
+ 	Oid			serverid;		/* OID of foreign server */
  	/* use struct pointer to avoid including fdwapi.h here */
  	struct FdwRoutine *fdwroutine;
  	void	   *fdw_state;		/* foreign-data wrapper can keep state here */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 824d8b5..0f5dac2 100644
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 67,72 ****
--- 67,73 ----
  	T_NestLoop,
  	T_MergeJoin,
  	T_HashJoin,
+ 	T_ForeignJoin,
  	T_Material,
  	T_Sort,
  	T_Group,
*************** typedef enum NodeTag
*** 221,226 ****
--- 222,228 ----
  	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 6685864..2fc8a36 100644
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
*************** typedef struct WorkTableScan
*** 467,472 ****
--- 467,473 ----
  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 a400960..d790a34 100644
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct RelOptInfo
*** 416,421 ****
--- 416,422 ----
  	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
*** 954,959 ****
--- 955,970 ----
  } 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 125808a..284eb92 100644
*** a/src/include/optimizer/cost.h
--- b/src/include/optimizer/cost.h
*************** extern bool enable_nestloop;
*** 61,66 ****
--- 61,67 ----
  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
*** 114,119 ****
--- 115,124 ----
  			   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 38c8c1c..b303270 100644
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern HashPath *create_hashjoin_path(Pl
*** 94,99 ****
--- 94,108 ----
  					 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 5f20c93..8a81674 100644
*** a/src/test/regress/expected/rangefuncs.out
--- b/src/test/regress/expected/rangefuncs.out
*************** SELECT name, setting FROM pg_settings WH
*** 2,7 ****
--- 2,8 ----
           name         | setting 
  ----------------------+---------
   enable_bitmapscan    | on
+  enable_foreignjoin   | on
   enable_hashagg       | on
   enable_hashjoin      | on
   enable_indexonlyscan | on
*************** SELECT name, setting FROM pg_settings WH
*** 12,18 ****
   enable_seqscan       | on
   enable_sort          | on
   enable_tidscan       | on
! (11 rows)
  
  CREATE TABLE foo2(fooid int, f2 int);
  INSERT INTO foo2 VALUES(1, 11);
--- 13,19 ----
   enable_seqscan       | on
   enable_sort          | on
   enable_tidscan       | on
! (12 rows)
  
  CREATE TABLE foo2(fooid int, f2 int);
  INSERT INTO foo2 VALUES(1, 11);
#16Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Shigeru Hanada (#15)
Re: WIP: Join push-down for foreign tables

On 15.11.2011 19:16, Shigeru Hanada wrote:

This is the second effort for $SUBJECT. Attached patch requires
pgsql_fdw patches[1] to be applied previously. This patch provides:

* Changes for backend
* Add new planner node ForeignJoinPath and related routines. In
current design, planner consider all of possible join combinations
between foreign tables, similar to local joins such as nested loop,
hash join and merge join. And if foreign join is cheapest, planner
produces a ForeignScan plan node for a join. So executor is not
modified heavily since 9.1.
* Add new FDW callback for planning join push-down between foreign
tables on same server. This function is optional, and allowed to
return NULL to tell planner that that join can't be handled by the
FDW.

So the way a three-way join is planned, is that the planner first asks
the FDW to plan ForeignPaths of scanning the individual tables. Then it
asks the FDW to consider pairwise joins of those ForeignPaths. Then it
asks the FDW to consider joins of the constructed ForeignPaths and
ForeignJoinPaths. Ie. the plan involving a join of three or more remote
tables is built bottom-up, just like a join of local tables.

When the FDW recognizes it's being asked to join a ForeignJoinPath and a
ForeignPath, or two ForeignJoinPaths, it throws away the old SQL it
constructed to do the two-way join, and builds a new one to join all
three tables. That seems tedious, when there are a lot of tables
involved. A FDW like the pgsql_fdw that constructs an SQL query doesn't
need to consider pairs of joins. It could just as well build the SQL for
the three-way join directly. I think the API needs to reflect that.

I wonder if we should have a heuristic to not even consider doing a join
locally, if it can be done remotely. For a query like this:

SELECT * FROM remote1 a, remote2 b, remote3 c WHERE a.id = b.id AND c.id
= b.id

it's quite obvious that the best plan is to do the join remotely, rather
than pull all the rows from all tables, and do the join locally. In
theory, if the remote database is remarkably bad at performing joins, it
might be faster to pull in all the data and do it locally, but I can't
really imagine that happening in practice.

* Changes for pgsql_fdw
* Implemente PlanForeignJoin callback function.

A couple of basic bugs I bumped into:

* WHERE-clause building fails on a cartesian product ("SELECT * FROM
remote1, remote2")

* The join planning in pgsql_fdw seems to get confused and gives up if
there are any local tables also involved in the query (e.g "explain
SELECT * FROM remote1, remote2 LEFT OUTER JOIN local1 on (local1.a =
remote2.a) WHERE remote1.a = remote2.a;")

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#17Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#16)
Re: WIP: Join push-down for foreign tables

Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:

When the FDW recognizes it's being asked to join a ForeignJoinPath and a
ForeignPath, or two ForeignJoinPaths, it throws away the old SQL it
constructed to do the two-way join, and builds a new one to join all
three tables.

It should certainly not "throw away" the old SQL --- that path could
still be chosen.

That seems tedious, when there are a lot of tables
involved. A FDW like the pgsql_fdw that constructs an SQL query doesn't
need to consider pairs of joins. It could just as well build the SQL for
the three-way join directly. I think the API needs to reflect that.

I wonder if we should have a heuristic to not even consider doing a join
locally, if it can be done remotely.

I think this is a bad idea. It will require major restructuring of the
planner, and sometimes it will fail to find the best plan, in return for
not much. The nature of join planning is that we investigate a lot of
dead ends.

regards, tom lane

#18Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Tom Lane (#17)
Re: WIP: Join push-down for foreign tables

On 17.11.2011 17:24, Tom Lane wrote:

Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:

When the FDW recognizes it's being asked to join a ForeignJoinPath and a
ForeignPath, or two ForeignJoinPaths, it throws away the old SQL it
constructed to do the two-way join, and builds a new one to join all
three tables.

It should certainly not "throw away" the old SQL --- that path could
still be chosen.

Right, that was loose phrasing from me.

That seems tedious, when there are a lot of tables
involved. A FDW like the pgsql_fdw that constructs an SQL query doesn't
need to consider pairs of joins. It could just as well build the SQL for
the three-way join directly. I think the API needs to reflect that.

Tom, what do you think of this part? I think it would be a lot more
natural API if the planner could directly ask the FDW to construct a
plan for a three (or more)-way join, instead of asking it to join a join
relation into another relation.

The proposed API is this:

+ FdwPlan *
+ PlanForeignJoin (Oid serverid,
+                  PlannerInfo *root,
+                  RelOptInfo *joinrel,
+                  JoinType jointype,
+                  SpecialJoinInfo *sjinfo,
+                  Path *outer_path,
+                  Path *inner_path,
+                  List *restrict_clauses,
+                  List *pathkeys);

The problem I have with this is that the FDW shouldn't need outer_path
and inner_path. All the information it needs is in 'joinrel'. Except for
outer-joins, I guess; is there convenient way to get the join types
involved in a join rel? It's there in SpecialJoinInfo, but if the FDW is
only passed the RelOptInfo representing the three-way join, it's not there.

Does the planner expect the result from the foreign server to be
correctly sorted, if it passes pathkeys to that function?

I wonder if we should have a heuristic to not even consider doing a join
locally, if it can be done remotely.

I think this is a bad idea. It will require major restructuring of the
planner, and sometimes it will fail to find the best plan, in return for
not much. The nature of join planning is that we investigate a lot of
dead ends.

Ok.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#19Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#18)
Re: WIP: Join push-down for foreign tables

Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:

Tom, what do you think of this part? I think it would be a lot more
natural API if the planner could directly ask the FDW to construct a
plan for a three (or more)-way join, instead of asking it to join a join
relation into another relation.

I think this is fundamentally not going to work, at least not without
major and IMO unwise surgery on the planner. Building up joins pairwise
is what it does.

Furthermore, you seem to be imagining that there is only one best path
for any join, which isn't the case. We'll typically have several paths
under consideration because of cheapest-startup versus cheapest-total
and/or different resulting sort orders. If we do what you're
suggesting, that's going to either break entirely or require a much more
complicated API for PlanForeignJoin.

Does the planner expect the result from the foreign server to be
correctly sorted, if it passes pathkeys to that function?

Well, the result path should be marked with pathkeys if it is known to
be sorted a certain way, or with NIL if not. There's no prejudgment as
to what a particular join method will produce. That does raise
interesting questions though as to how to interact with the remote-end
planner --- if we've reported that a path has certain pathkeys, that
probably means that the SQL sent to the remote had better say ORDER BY,
which would be kind of annoying if in the end we weren't depending on
the path to be sorted. I'm not sure what it would take to pass that
information back down, though. What we might have to do to make this
work conveniently is generate two versions of every foreign path: one
marked with pathkeys, and one not. And make sure the former has a
somewhat-higher cost. Then we'd know from which path gets picked
whether the plan is actually depending on sorted results.

regards, tom lane

#20Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Tom Lane (#19)
Re: WIP: Join push-down for foreign tables

On 02.12.2011 18:55, Tom Lane wrote:

Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:

Tom, what do you think of this part? I think it would be a lot more
natural API if the planner could directly ask the FDW to construct a
plan for a three (or more)-way join, instead of asking it to join a join
relation into another relation.

I think this is fundamentally not going to work, at least not without
major and IMO unwise surgery on the planner. Building up joins pairwise
is what it does.

Furthermore, you seem to be imagining that there is only one best path
for any join, which isn't the case.

No, I understand that the planner considers many alternatives, even at
the same time, because of different output sort orders and startup vs.
total cost. I'm imagining that the planner would ask the FDW to
construct the two-way joins, and consider joining the results of those
locally to the third table, and also ask the FDW to construct the
three-way join as whole. And then choose the cheapest alternative.

We'll typically have several paths
under consideration because of cheapest-startup versus cheapest-total
and/or different resulting sort orders. If we do what you're
suggesting, that's going to either break entirely or require a much more
complicated API for PlanForeignJoin.

I don't understand why the FDW should care about the order the joins are
constructed in in the planner. From the FDW's point of view, there's no
difference between joining ((A B) C) and (A (B C)). Unless you also want
to consider doing a remote join between (A B) and C, where C is a
foreign table but A and B are local tables. That would in theory be
possible to execute in the remote server, by shipping the result of (A
B) to the remote server, but we'd also need a quite different executor
API to handle that.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#21Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#20)
Re: WIP: Join push-down for foreign tables

Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:

On 02.12.2011 18:55, Tom Lane wrote:

Furthermore, you seem to be imagining that there is only one best path
for any join, which isn't the case.

No, I understand that the planner considers many alternatives, even at
the same time, because of different output sort orders and startup vs.
total cost. I'm imagining that the planner would ask the FDW to
construct the two-way joins, and consider joining the results of those
locally to the third table, and also ask the FDW to construct the
three-way join as whole. And then choose the cheapest alternative.

It probably makes sense to turn control over to the FDW just once to
consider all possible foreign join types for a given join pair, ie
we don't want to ask it separately about nestloop, hash, merge joins.
But then we had better be able to let it generate multiple paths within
the one call, and dump them all to add_path. You're still assuming that
there is one unique best path for any join, and *that is not the case*,
or at least we don't know which will be the best at the time we're
generating join paths. We don't know whether fast-start is better than
cheapest-total, nor which sort order might be the best, until we get up
to the highest join level.

So rather than returning a Path struct, it would have to just be given
the joinrel struct and be expected to do add_path call(s) for itself.

I don't understand why the FDW should care about the order the joins are
constructed in in the planner. From the FDW's point of view, there's no
difference between joining ((A B) C) and (A (B C)).

Maybe there is, maybe there isn't. You're assuming too much about how
the FDW does its join planning, I think --- in particular, FDWs that are
backed by less than a Postgres-equivalent remote planner might well
appreciate being walked through all the feasible join pairs.

If we do it as I suggest above, the FDW could remember that it had
already planned this joinrel and just drop out immediately if called
again, if it's going to do it the way you're thinking.

regards, tom lane

#22Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Tom Lane (#21)
Re: WIP: Join push-down for foreign tables

On 03.12.2011 00:24, Tom Lane wrote:

Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:

On 02.12.2011 18:55, Tom Lane wrote:

Furthermore, you seem to be imagining that there is only one best path
for any join, which isn't the case.

No, I understand that the planner considers many alternatives, even at
the same time, because of different output sort orders and startup vs.
total cost. I'm imagining that the planner would ask the FDW to
construct the two-way joins, and consider joining the results of those
locally to the third table, and also ask the FDW to construct the
three-way join as whole. And then choose the cheapest alternative.

It probably makes sense to turn control over to the FDW just once to
consider all possible foreign join types for a given join pair, ie
we don't want to ask it separately about nestloop, hash, merge joins.
But then we had better be able to let it generate multiple paths within
the one call, and dump them all to add_path. You're still assuming that
there is one unique best path for any join, and *that is not the case*,
or at least we don't know which will be the best at the time we're
generating join paths. We don't know whether fast-start is better than
cheapest-total, nor which sort order might be the best, until we get up
to the highest join level.

Hmm, so you're saying that the FDW function needs to be able to return
multiple paths for a single joinrel. Fair enough, and that's not
specific to remote joins. Even a single-table foreign scan could be
implemented differently depending on whether you prefer fast-start or
cheapest total.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#23Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#22)
Re: WIP: Join push-down for foreign tables

Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:

Hmm, so you're saying that the FDW function needs to be able to return
multiple paths for a single joinrel. Fair enough, and that's not
specific to remote joins. Even a single-table foreign scan could be
implemented differently depending on whether you prefer fast-start or
cheapest total.

... or ordered vs unordered, etc. Yeah, good point, we already got this
wrong with the PlanForeignScan API. Good thing we didn't promise that
would be stable.

regards, tom lane

#24Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Tom Lane (#23)
Re: WIP: Join push-down for foreign tables

On 03.12.2011 01:05, Tom Lane wrote:

Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:

Hmm, so you're saying that the FDW function needs to be able to return
multiple paths for a single joinrel. Fair enough, and that's not
specific to remote joins. Even a single-table foreign scan could be
implemented differently depending on whether you prefer fast-start or
cheapest total.

... or ordered vs unordered, etc. Yeah, good point, we already got this
wrong with the PlanForeignScan API. Good thing we didn't promise that
would be stable.

This discussion withered down here...

I think the advice to Shigeru-san is to work on the API. We didn't reach
a consensus on what exactly it should look like, but at least you need
to be able to return multiple paths for a single joinrel, and should
look at fixing the PlanForeignScan API to allow that too.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com