20110224-postgresql_fdw.patch

application/octet-stream

Filename: 20110224-postgresql_fdw.patch
Type: application/octet-stream
Part: 0
Message: Re: PostgreSQL FDW update

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
File+
contrib/Makefile 1 0
contrib/postgresql_fdw/expected/postgresql_fdw.out 141 0
contrib/postgresql_fdw/.gitignore 3 0
contrib/postgresql_fdw/Makefile 23 0
contrib/postgresql_fdw/postgresql_fdw--1.0.sql 11 0
contrib/postgresql_fdw/postgresql_fdw.c 1037 0
contrib/postgresql_fdw/postgresql_fdw.control 5 0
contrib/postgresql_fdw/postgresql_fdw.h 18 0
contrib/postgresql_fdw/sql/postgresql_fdw.sql 84 0
contrib/postgresql_fdw/uninstall_postgresql_fdw.sql 7 0
contrib/README 3 0
doc/src/sgml/contrib.sgml 1 0
doc/src/sgml/filelist.sgml 1 0
doc/src/sgml/postgresql-fdw.sgml 166 0
src/backend/foreign/foreign.c 45 24
src/include/foreign/foreign.h 1 0
diff --git a/contrib/Makefile b/contrib/Makefile
index 6967767..62811bb 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -42,6 +42,7 @@ SUBDIRS = \
 		pgcrypto	\
 		pgrowlocks	\
 		pgstattuple	\
+		postgresql_fdw	\
 		seg		\
 		spi		\
 		tablefunc	\
diff --git a/contrib/README b/contrib/README
index 9bd1b13..e6d565f 100644
--- a/contrib/README
+++ b/contrib/README
@@ -163,6 +163,9 @@ pgstattuple -
 	space within a table
 	by Tatsuo Ishii <ishii@sraoss.co.jp>
 
+postgresql_fdw -
+	Foreign-data wrapper for external PostgreSQL server.
+
 seg -
 	Confidence-interval datatype (GiST indexing example)
 	by Gene Selkov, Jr. <selkovjr@mcs.anl.gov>
diff --git a/contrib/postgresql_fdw/.gitignore b/contrib/postgresql_fdw/.gitignore
new file mode 100644
index 0000000..4a8bf17
--- /dev/null
+++ b/contrib/postgresql_fdw/.gitignore
@@ -0,0 +1,3 @@
+/postgresql_fdw.sql
+# Generated subdirectories
+/results/
diff --git a/contrib/postgresql_fdw/Makefile b/contrib/postgresql_fdw/Makefile
new file mode 100644
index 0000000..da3c845
--- /dev/null
+++ b/contrib/postgresql_fdw/Makefile
@@ -0,0 +1,23 @@
+# contrib/postgresql_fdw/Makefile
+
+MODULE_big = postgresql_fdw
+PG_CPPFLAGS = -I$(libpq_srcdir)
+OBJS	= postgresql_fdw.o
+SHLIB_LINK = $(libpq)
+SHLIB_PREREQS = submake-libpq
+
+EXTENSION = postgresql_fdw
+DATA = postgresql_fdw--1.0.sql postgresql_fdw--unpachaged--1.0.sql
+
+REGRESS = postgresql_fdw
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/postgresql_fdw
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/postgresql_fdw/expected/postgresql_fdw.out b/contrib/postgresql_fdw/expected/postgresql_fdw.out
new file mode 100644
index 0000000..05b3914
--- /dev/null
+++ b/contrib/postgresql_fdw/expected/postgresql_fdw.out
@@ -0,0 +1,141 @@
+SET DATESTYLE = 'Postgres, MDY';
+-- =============================================================================
+-- Prepare section
+-- =============================================================================
+CREATE EXTENSION postgresql_fdw;
+-- connect database for regression test
+\c contrib_regression
+-- define fdw-related objects
+CREATE SERVER loopback1 FOREIGN DATA WRAPPER postgresql_fdw
+	OPTIONS (dbname 'contrib_regression');
+CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgresql_fdw
+	OPTIONS (dbname 'contrib_regression');
+CREATE USER MAPPING FOR PUBLIC SERVER loopback1;
+CREATE USER MAPPING FOR PUBLIC SERVER loopback2 OPTIONS (user 'invalid');
+CREATE TABLE t1(
+	c1 integer,
+	c2 text,
+	c3 date
+);
+COPY t1 FROM stdin;
+CREATE TABLE t2(
+	c1 integer,
+	c2 text,
+	c3 date
+);
+COPY t2 FROM stdin;
+CREATE FOREIGN TABLE ft1 (
+	c1 integer,
+	c2 text,
+	c3 date
+) SERVER loopback1 OPTIONS (relname 't1');
+CREATE FOREIGN TABLE ft2 (
+	c1 integer,
+	c2 text,
+	c3 date
+) SERVER loopback2 OPTIONS (relname 'invalid');
+-- simple query and connection caching
+SELECT ft1.* FROM ft1 ORDER BY c1;
+ c1 | c2  |     c3     
+----+-----+------------
+  1 | foo | 01-01-1970
+  2 | bar | 01-02-1970
+  3 | buz | 01-03-1970
+(3 rows)
+
+SELECT * FROM ft2 ORDER BY c1; -- ERROR
+ERROR:  could not connect to server "loopback2"
+DETAIL:  FATAL:  role "invalid" does not exist
+
+ALTER USER MAPPING FOR PUBLIC SERVER loopback2 OPTIONS (DROP user);
+SELECT * FROM ft2 ORDER BY c1; -- ERROR
+ERROR:  could not execute foreign query
+DETAIL:  ERROR:  relation "public.invalid" does not exist
+LINE 1: SELECT c1, c2, c3 FROM public.invalid
+                               ^
+
+HINT:  SELECT c1, c2, c3 FROM public.invalid
+ALTER FOREIGN TABLE ft2 OPTIONS (SET relname 't2');
+SELECT * FROM ft2 ORDER BY c1;
+ c1 | c2  |     c3     
+----+-----+------------
+  1 | foo | 01-01-1970
+ 12 | bar | 01-02-1970
+ 13 | buz | 01-03-1970
+(3 rows)
+
+-- query with projection
+SELECT c1 FROM ft1 ORDER BY c1;
+ c1 
+----
+  1
+  2
+  3
+(3 rows)
+
+-- join two foreign tables
+SELECT * FROM ft1 JOIN ft2 ON (ft1.c1 = ft2.c1) ORDER BY ft1.c1;
+ c1 | c2  |     c3     | c1 | c2  |     c3     
+----+-----+------------+----+-----+------------
+  1 | foo | 01-01-1970 |  1 | foo | 01-01-1970
+(1 row)
+
+-- join itself
+SELECT * FROM ft1 t1 JOIN ft1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1;
+ c1 | c2  |     c3     | c1 | c2  |     c3     
+----+-----+------------+----+-----+------------
+  1 | foo | 01-01-1970 |  1 | foo | 01-01-1970
+  2 | bar | 01-02-1970 |  2 | bar | 01-02-1970
+  3 | buz | 01-03-1970 |  3 | buz | 01-03-1970
+(3 rows)
+
+-- outer join
+SELECT * FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY 1,2,3,4,5,6;
+ c1 | c2  |     c3     | c1 | c2  |     c3     
+----+-----+------------+----+-----+------------
+  1 | foo | 01-01-1970 |  1 | foo | 01-01-1970
+  2 | bar | 01-02-1970 |    |     | 
+  3 | buz | 01-03-1970 |    |     | 
+(3 rows)
+
+-- WHERE clause push-down
+SELECT * FROM ft1 WHERE c1 = 1 AND c2 = lower('FOO') AND c3 < now() and c3 < clock_timestamp();
+ c1 | c2  |     c3     
+----+-----+------------
+  1 | foo | 01-01-1970
+(1 row)
+
+EXPLAIN (COSTS FALSE) SELECT * FROM ft1 WHERE c1 = 1 AND c2 = lower('FOO') AND c3 < now() and c3 < clock_timestamp();
+                                               QUERY PLAN                                                
+---------------------------------------------------------------------------------------------------------
+ Foreign Scan on ft1
+   Filter: (c3 < clock_timestamp())
+   Remote SQL: SELECT c1, c2, c3 FROM public.t1 WHERE ((c3 < now()) AND (c1 = 1) AND (c2 = 'foo'::text))
+(3 rows)
+
+-- prepared statement
+PREPARE st(int) AS SELECT * FROM ft1 WHERE c1 > $1 ORDER BY c1;
+EXECUTE st(1);
+ c1 | c2  |     c3     
+----+-----+------------
+  2 | bar | 01-02-1970
+  3 | buz | 01-03-1970
+(2 rows)
+
+EXECUTE st(2);
+ c1 | c2  |     c3     
+----+-----+------------
+  3 | buz | 01-03-1970
+(1 row)
+
+DEALLOCATE st;
+-- clean up
+DROP TABLE t1 CASCADE;
+DROP EXTENSION postgresql_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 foreign table ft1
+drop cascades to server loopback2
+drop cascades to user mapping for public
+drop cascades to foreign table ft2
diff --git a/contrib/postgresql_fdw/postgresql_fdw--1.0.sql b/contrib/postgresql_fdw/postgresql_fdw--1.0.sql
new file mode 100644
index 0000000..37facd4
--- /dev/null
+++ b/contrib/postgresql_fdw/postgresql_fdw--1.0.sql
@@ -0,0 +1,11 @@
+/* contrib/postgresql_fdw/postgresql_fdw--1.0.sql */
+
+CREATE OR REPLACE FUNCTION postgresql_fdw_handler ()
+RETURNS fdw_handler
+AS 'MODULE_PATHNAME','postgresql_fdw_handler'
+LANGUAGE C STRICT;
+
+CREATE FOREIGN DATA WRAPPER postgresql_fdw
+VALIDATOR postgresql_fdw_validator
+HANDLER postgresql_fdw_handler;
+
diff --git a/contrib/postgresql_fdw/postgresql_fdw.c b/contrib/postgresql_fdw/postgresql_fdw.c
new file mode 100644
index 0000000..89b5790
--- /dev/null
+++ b/contrib/postgresql_fdw/postgresql_fdw.c
@@ -0,0 +1,1037 @@
+/*-------------------------------------------------------------------------
+ *
+ * postgresql_fdw.c
+ *		  foreign-data wrapper for PostgreSQL
+ *
+ * Portions Copyright (c) 2010-2011, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  contrib/postgresql_fdw/postgresql_fdw.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
+#include "commands/explain.h"
+#include "foreign/fdwapi.h"
+#include "foreign/foreign.h"
+#include "funcapi.h"
+#include "libpq-fe.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/relation.h"
+#include "optimizer/clauses.h"
+#include "optimizer/cost.h"
+#include "optimizer/plancat.h"
+#include "optimizer/var.h"
+#include "parser/parsetree.h"
+#include "parser/scansup.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/syscache.h"
+
+#include "postgresql_fdw.h"
+
+PG_MODULE_MAGIC;
+
+extern Datum postgresql_fdw_handler(PG_FUNCTION_ARGS);
+
+/*
+ * FDW routines
+ */
+static FdwPlan *pgPlanForeignScan(Oid foreigntableid, PlannerInfo *root,
+								  RelOptInfo *baserel);
+static void pgExplainForeignScan(ForeignScanState *node,
+								 struct ExplainState *es);
+static void pgBeginForeignScan(ForeignScanState *node, int eflags);
+static TupleTableSlot *pgIterateForeignScan(ForeignScanState *node);
+static void pgReScanForeignScan(ForeignScanState *node);
+static void pgEndForeignScan(ForeignScanState *node);
+
+/* helper for deparsing a request into SQL statement */
+static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel,
+						   Cost *startup_cost, Cost *total_cost);
+
+static bool is_foreign_qual(PlannerInfo *root, RelOptInfo *baserel, Expr *expr);
+static bool foreign_qual_walker(Node *node, void *context);
+static char *deparseSql(Oid foreigntableid, PlannerInfo *root,
+						RelOptInfo *baserel);
+
+static void storeResult(TupleTableSlot *slot, PGresult *res, int rowno);
+
+/*
+ * Connection management
+ */
+static PGconn *GetConnection(ForeignServer *server, UserMapping *user);
+#ifdef NOT_USED
+static void ReleaseConnection(PGconn *conn);
+#endif
+static void check_conn_params(const char **keywords, const char **values);
+static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
+static void cleanup_connection(ResourceReleasePhase phase,
+							   bool isCommit,
+							   bool isTopLevel,
+							   void *arg);
+
+/*
+ * PostgreSQL specific portion of a foreign query request
+ */
+typedef struct pgFdwExecutionState
+{
+	PGconn	   *conn;		/* connection used for the scan */
+	PGresult   *res;		/* result of the scan, held until the scan ends */
+	int			nextrow;	/* row index to be returned in next fetch */
+} pgFdwExecutionState;
+
+
+/*
+ * return foreign-data wrapper handler object to execute foreign-data wrapper
+ * routines.
+ */
+PG_FUNCTION_INFO_V1(postgresql_fdw_handler);
+Datum
+postgresql_fdw_handler(PG_FUNCTION_ARGS)
+{
+	FdwRoutine *fdwroutine = makeNode(FdwRoutine);
+
+	fdwroutine->PlanForeignScan = pgPlanForeignScan;
+	fdwroutine->ExplainForeignScan = pgExplainForeignScan;
+	fdwroutine->BeginForeignScan = pgBeginForeignScan;
+	fdwroutine->IterateForeignScan = pgIterateForeignScan;
+	fdwroutine->ReScanForeignScan = pgReScanForeignScan;
+	fdwroutine->EndForeignScan = pgEndForeignScan;
+
+	PG_RETURN_POINTER(fdwroutine);
+}
+
+/*
+ * pgPlanForeignScan
+ *		Create FdwPlan for the scan
+ */
+static FdwPlan *
+pgPlanForeignScan(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel)
+{
+	FdwPlan	   *fdwplan;
+	char	   *sql;
+
+	fdwplan = makeNode(FdwPlan);
+
+	sql = deparseSql(foreigntableid, root, baserel);
+
+	estimate_costs(root, baserel, &fdwplan->startup_cost,
+					&fdwplan->total_cost);
+
+	fdwplan->fdw_private = lappend(NIL, makeString(sql));
+
+	return fdwplan;
+}
+
+/*
+ * pgExplainForeignScan
+ *		Produce extra output for EXPLAIN
+ */
+static void 
+pgExplainForeignScan(ForeignScanState *node, struct ExplainState *es)
+{
+	FdwPlan	   *fdwplan;
+	char	   *sql;
+
+	fdwplan = ((ForeignScan *) node->ss.ps.plan)->fdwplan;
+	sql = strVal(list_nth(fdwplan->fdw_private, 0));
+	ExplainPropertyText("Remote SQL", sql, es);
+}
+
+/*
+ * return true if node can NOT be evaluatated in foreign server.
+ */
+static bool
+foreign_qual_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Param:
+			/* TODO: pass internal parameters to the foreign server */
+			{
+				ParamKind	paramkind = ((Param *) node)->paramkind;
+				elog(DEBUG1, "%s() param=%s", __FUNCTION__,
+	  				paramkind == PARAM_EXTERN ? "PARAM_EXTERN" :
+	  				paramkind == PARAM_EXEC ? "PARAM_EXEC" :
+	  				paramkind == PARAM_SUBLINK ? "PARAM_SUBLINK" : "unkown");
+			}
+			if (((Param *) node)->paramkind != PARAM_EXTERN)
+				return true;
+			break;
+		case T_DistinctExpr:
+		case T_OpExpr:
+		case T_ScalarArrayOpExpr:
+		case T_FuncExpr:
+			/*
+			 * If the qual contains any mutable function, the whole expression
+			 * should be evaluated on local side.
+			 */
+			if (contain_mutable_functions(node))
+				return true;
+			break;
+		case T_TargetEntry:
+		case T_PlaceHolderVar:
+		case T_AppendRelInfo:
+		case T_PlaceHolderInfo:
+			/* TODO: research whether those complex nodes are evaluatable. */
+			return true;
+		default:
+			break;
+	}
+
+	return expression_tree_walker(node, foreign_qual_walker, context);
+}
+
+
+static void
+deparse_var(PlannerInfo *root, StringInfo buf, Var *var)
+{
+	RangeTblEntry *rte;
+	char *attname;
+
+	if (var->varlevelsup != 0)
+		elog(ERROR, "unexpected varlevelsup %d in remote query",
+			 var->varlevelsup);
+
+	if (var->varno < 1 || var->varno > list_length(root->parse->rtable))
+		elog(ERROR, "unexpected varno %d in remote query", var->varno);
+	rte = rt_fetch(var->varno, root->parse->rtable);
+
+	attname  = get_rte_attribute_name(rte, var->varattno);
+	appendStringInfoString(buf, quote_identifier(attname));
+}
+
+typedef struct
+{
+	PlannerInfo *root;
+	RelOptInfo *foreignrel;
+} remotely_executable_cxt;
+
+static bool
+is_proc_remotely_executable(Oid procid)
+{
+	/* assume that only built-in functions can be pushed down */
+	if (get_func_namespace(procid) != PG_CATALOG_NAMESPACE)
+		return false;
+	/* we don't check volatility here, that's done once at the top-level */
+
+	return true;
+}
+
+static bool
+is_not_remotely_executable_walker(Node *node, remotely_executable_cxt *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+		return true;
+
+	switch (nodeTag(node))
+	{
+		case T_Query:
+			/* give up on subqueries */
+			return true;
+		case T_Param:
+			/* TODO: pass internal parameters to the foreign server */
+			{
+				ParamKind	paramkind = ((Param *) node)->paramkind;
+				elog(DEBUG1, "%s() param=%s", __FUNCTION__,
+	  				paramkind == PARAM_EXTERN ? "PARAM_EXTERN" :
+	  				paramkind == PARAM_EXEC ? "PARAM_EXEC" :
+	  				paramkind == PARAM_SUBLINK ? "PARAM_SUBLINK" : "unkown");
+			}
+			if (((Param *) node)->paramkind != PARAM_EXTERN)
+				return true;
+			break;
+		case T_OpExpr:
+			{
+				OpExpr *op = (OpExpr *) node;
+				if (!is_proc_remotely_executable(op->opfuncid))
+					return true;
+				else
+					return false;
+			}
+		case T_FuncExpr:
+			{
+				FuncExpr *fe = (FuncExpr *) node;
+				if (!is_proc_remotely_executable(fe->funcid))
+					return true;
+				else
+					return false;
+			}
+
+		case T_TargetEntry:
+		case T_PlaceHolderVar:
+		case T_AppendRelInfo:
+		case T_PlaceHolderInfo:
+			/* TODO: research whether those complex nodes are evaluatable. */
+			return true;
+		case T_Var:
+			{
+				Var *var = (Var *) node;
+				if (var->varno != context->foreignrel->relid || var->varlevelsup != 0)
+					return true;
+				else
+					return false;
+			}
+
+		default:
+			break;
+	}
+
+	return expression_tree_walker(node, foreign_qual_walker, context);
+}
+
+
+/*
+ * Check whether the ExprState node can be evaluated in foreign server.
+ *
+ * An expression which consists of expressions below can be evaluated in
+ * the foreign server.
+ *  - constant value
+ *  - variable (foreign table column)
+ *  - external parameter (parameter of prepared statement)
+ *  - array
+ *  - bool expression (AND/OR/NOT)
+ *  - NULL test (IS [NOT] NULL)
+ *  - operator
+ *    - IMMUTABLE only
+ *    - It is required that the meaning of the operator be the same as the
+ *      local server in the foreign server. 
+ *  - function
+ *    - IMMUTABLE only
+ *    - It is required that the meaning of the operator be the same as the
+ *      local server in the foreign server. 
+ *  - scalar array operator (ANY/ALL)
+ */
+static bool
+is_foreign_qual(PlannerInfo *root, RelOptInfo *baserel, Expr *expr)
+{
+	remotely_executable_cxt context;
+
+	context.root = root;
+	context.foreignrel = baserel;
+	if (is_not_remotely_executable_walker((Node *) expr, &context))
+		return false;
+	if (contain_volatile_functions((Node *) expr))
+		return false;
+
+	return true;
+}
+
+/*
+ * Deparse query request into SQL statement.
+ *
+ * If an expression in PlanState.qual list satisfies is_foreign_qual(), the
+ * expression is:
+ *   - deparsed into WHERE clause of remote SQL statement to evaluate that
+ *     expression on remote side
+ *   - removed from PlanState.qual list to avoid duplicate evaluation, on
+ *     remote side and local side
+ */
+static char *
+deparseSql(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel)
+{
+	AttrNumber		attr;
+	List		   *attr_used = NIL;
+	List		   *context;
+	StringInfoData	sql;
+	ForeignTable   *table = GetForeignTable(foreigntableid);
+	ListCell   *lc;
+	bool		first;
+	char	   *nspname = NULL;
+	char	   *relname = NULL;
+
+	/* extract ForeignScan and RangeTblEntry */
+
+	/* prepare to deparse plan */
+	initStringInfo(&sql);
+
+	context = deparse_context_for("foreigntable", foreigntableid);
+
+	/* Collect used columns from restrict information and target list */
+	attr_used = list_union(attr_used, baserel->reltargetlist);
+	foreach (lc, baserel->baserestrictinfo)
+	{
+		List		   *l;
+		RestrictInfo   *ri = lfirst(lc);
+
+		l = pull_var_clause((Node *) ri->clause, PVC_RECURSE_PLACEHOLDERS);
+		attr_used = list_union(attr_used, l);
+	}
+
+	/* deparse SELECT target list */
+	appendStringInfoString(&sql, "SELECT ");
+	first = true;
+	for (attr = 1; attr <= baserel->max_attr; attr++)
+	{
+		Var *var;
+
+		if (!first)
+			appendStringInfoString(&sql, ", ");
+		first = false;
+
+		/* Use "NULL" for unused columns. */
+		foreach (lc, attr_used)
+		{
+			var = lfirst(lc);
+			if (var->varattno == attr)
+				break;
+			var = NULL;
+		}
+
+		if (var != NULL)
+			deparse_var(root, &sql, var);
+		else
+			appendStringInfo(&sql, "NULL");
+	}
+
+	/*
+	 * Deparse FROM
+	 *
+	 * If the foreign table has generic option "nspname" and/or "relname", use
+	 * them in the foreign query.  Otherwise, use local catalog names.
+	 */
+	foreach(lc, table->options)
+	{
+		DefElem *opt = lfirst(lc);
+		if (strcmp(opt->defname, "nspname") == 0)
+			nspname = pstrdup(strVal(opt->arg));
+		else if (strcmp(opt->defname, "relname") == 0)
+			relname = pstrdup(strVal(opt->arg));
+	}
+	if (nspname == NULL)
+		nspname = get_namespace_name(get_rel_namespace(foreigntableid));
+	if (relname == NULL)
+		relname = get_rel_name(foreigntableid);
+	appendStringInfo(&sql, " FROM %s.%s",
+					 quote_identifier(nspname), 
+					 quote_identifier(relname));
+	
+
+	/*
+	 * deparse WHERE cluase
+	 *
+	 * The expressions which satisfy is_foreign_qual() are deparsed into WHERE
+	 * clause of result SQL string, and they could be removed from qual of
+	 * PlanState to avoid duplicate evaluation at ExecScan().
+	 *
+	 * We never change the qual in the Plan node which was made by PREPARE
+	 * statement to make following EXECUTE statements work properly.  The Plan
+	 * node is used repeatedly to create PlanState for each EXECUTE statement.
+	 */
+	if (baserel->baserestrictinfo)
+	{
+		List	   *local_qual = NIL;
+		List	   *foreign_expr = NIL;
+		ListCell   *lc;
+
+		/*
+		 * Divide qual of PlanState into two lists, one for local evaluation
+		 * and one for foreign evaluation.
+		 */
+		foreach (lc, baserel->baserestrictinfo)
+		{
+			RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
+
+			if (is_foreign_qual(root, baserel, ri->clause))
+			{
+				/* XXX: deparse and add to sql here */
+				foreign_expr = lappend(foreign_expr, ri->clause);
+			}
+			else
+				local_qual = lappend(local_qual, ri);
+		}
+		/*
+		 * XXX: If the remote side is not reliable enough, we can keep the qual
+		 * in PlanState as is and evaluate them on local side too.  If so, just
+		 * omit replacement below.
+		 */
+		baserel->baserestrictinfo = local_qual;
+
+		/*
+		 * Deparse quals to be evaluated in the foreign server if any.
+		 * TODO: modify deparse_expression() to deparse conditions which use
+		 * internal parameters.
+		 */
+		if (foreign_expr != NIL)
+		{
+			Node   *node;
+			node = (Node *) make_ands_explicit(foreign_expr);
+			appendStringInfo(&sql, " WHERE %s",
+				deparse_expression(node, context, false, false));
+			/*
+			 * The contents of the list MUST NOT be free-ed because they are
+			 * referenced from Plan.qual list.
+			 */
+			list_free(foreign_expr);
+		}
+	}
+
+	elog(DEBUG1, "deparsed SQL is \"%s\"", sql.data);
+
+	return sql.data;
+}
+
+/*
+ * Initiate actual scan on a foreign table.
+ */
+static void
+pgBeginForeignScan(ForeignScanState *node, int eflags)
+{
+	pgFdwExecutionState *festate;
+
+	Oid				relid;
+	ForeignTable   *table;
+	ForeignServer  *server;
+	UserMapping	   *user;
+
+	FdwPlan		   *fdwplan;
+	const char	   *sql;
+	PGconn		   *conn;
+	PGresult	   *res;
+	ParamListInfo	params = node->ss.ps.state->es_param_list_info;
+	int			numParams = params ? params->numParams : 0;
+	Oid		   *types = NULL;
+	const char **values = NULL;
+
+	elog(DEBUG3, "%s() called", __FUNCTION__);
+
+	/*
+	 * Do nothing in EXPLAIN (no ANALYZE) case.  node->fdw_state stays NULL.
+	 */
+	if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
+		return;
+
+	festate = palloc(sizeof(pgFdwExecutionState));
+
+	/* Get connection to external PostgreSQL server. */
+	relid = RelationGetRelid(node->ss.ss_currentRelation);
+	table = GetForeignTable(relid);
+	server = GetForeignServer(table->serverid);
+	user = GetUserMapping(GetOuterUserId(), table->serverid);
+	conn = GetConnection(server, user);
+	festate->conn = conn;
+
+	/* construct parameter array in text format */
+	/* TODO: omit unused parameter */
+	if (numParams > 0)
+	{
+		int			i;
+
+		types = palloc0(sizeof(Oid) * numParams);
+		values = palloc0(sizeof(char *) * numParams);
+		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;
+
+				/* TODO: send parameters in binary format rather than text */
+				getTypeOutputInfo(types[i], &out_func_oid, &isvarlena);
+				fmgr_info(out_func_oid, &func);
+				values[i] =
+					OutputFunctionCall(&func, params->params[i].value);
+			}
+		}
+	}
+
+	/*
+	 * Execute query with the parameters.
+	 * TODO: support internal parameters(PARAM_EXTERN)
+	 * TODO: support cursor mode for huge result sets.
+	 */
+	fdwplan = ((ForeignScan *) node->ss.ps.plan)->fdwplan;
+	sql = strVal(list_nth(fdwplan->fdw_private, 0));
+	res = PQexecParams(conn, sql, numParams, types, values, NULL, NULL, 0);
+	if (numParams > 0)
+	{
+		int i;
+		pfree(types);
+		for (i = 0; i < numParams; i++)
+			pfree((char *) values[i]);
+		pfree(values);
+	}
+
+	/*
+	 * If the query has failed, reporting details is enough here.
+	 * Connections which are used by this query (including other scans) will
+	 * be cleaned up by the foreign connection manager.
+	 */
+	if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
+	{
+		char *msg;
+
+		msg = pstrdup(PQerrorMessage(conn));
+		PQclear(res);
+		ereport(ERROR,
+				(errmsg("could not execute foreign query"),
+				 errdetail("%s", msg),
+				 errhint("%s", sql)));
+	}
+
+	festate->res = res;
+	festate->nextrow = 0;
+
+	node->fdw_state = (void *) festate;
+}
+
+/*
+ * return tuples one by one.
+ *   - execute SQL statement which was deparsed in pgBeginForeignScan()
+ *
+ * The all of result are fetched at once when pgIterateForeignScan() is called
+ * first after pgBeginForeignScan() or pgReScanForeignScan().
+ * pgIterateForeignScan() moves the next tuple from tupstore to TupleTableSlot
+ * in ScanState.
+ */
+static TupleTableSlot *
+pgIterateForeignScan(ForeignScanState *node)
+{
+	TupleTableSlot	   *slot = node->ss.ss_ScanTupleSlot;
+	pgFdwExecutionState *festate = (pgFdwExecutionState *) node->fdw_state;
+
+	elog(DEBUG3, "%s() called", __FUNCTION__);
+
+	if (festate->nextrow == PQntuples(festate->res))
+		ExecClearTuple(slot);
+	else
+		storeResult(slot, festate->res, festate->nextrow++);
+
+	return slot;
+}
+
+static void
+pgReScanForeignScan(ForeignScanState *node)
+{
+	((pgFdwExecutionState *) node->fdw_state)->nextrow = 0;
+}
+
+static void
+pgEndForeignScan(ForeignScanState *node)
+{
+	pgFdwExecutionState *festate = (pgFdwExecutionState *) node->fdw_state;
+
+	if (festate == NULL)
+		return;
+
+	PQclear(festate->res);
+	pfree(festate);
+}
+
+/*
+ * Store a PGresult into tuplestore.
+ */
+static void
+storeResult(TupleTableSlot *slot, PGresult *res, int rowno)
+{
+	int					i;
+	int					nfields;
+	int					attnum;		/* number of non-dropped columns */
+	char			  **values;
+	AttInMetadata	   *attinmeta;
+	Form_pg_attribute  *attrs;
+	TupleDesc	tupdesc = slot->tts_tupleDescriptor;
+
+	nfields = PQnfields(res);
+	attrs = tupdesc->attrs;
+
+	/* 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)));
+
+	/* buffer should include dropped columns */
+	values = palloc(sizeof(char *) * tupdesc->natts);
+
+	/* put all tuples into the tuplestore */
+	attinmeta = TupleDescGetAttInMetadata(tupdesc);
+	{
+		int			j;
+		HeapTuple	tuple;
+
+		for (i = 0, j = 0; i < tupdesc->natts; i++)
+		{
+			/* skip dropped columns. */
+			if (attrs[i]->attisdropped)
+			{
+				values[i] = NULL;
+				continue;
+			}
+
+			if (PQgetisnull(res, rowno, j))
+				values[i] = NULL;
+			else
+				values[i] = PQgetvalue(res, rowno, 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(attinmeta, values);
+		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+	}
+
+	pfree(values);
+}
+
+
+/*
+ * Estimate costs of scanning on a foreign table.
+ *
+ * baserel->baserestrictinfo can be used to examine quals on the relation.
+ */
+static void
+estimate_costs(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, Cost *total_cost)
+{
+	RangeTblEntry  *rte;
+	int				width;
+	double			connection_cost = 0.0;
+	double			transfer_cost = 0.0;
+
+	elog(DEBUG3, "%s() called", __FUNCTION__);
+
+	/*
+	 * Use cost_seqscan() to get approximate value.
+	 */
+	rte = planner_rt_fetch(baserel->relid, root);
+//	cost_seqscan(&path->path, root, baserel);
+
+	width = get_relation_data_width(rte->relid, NULL);
+
+	/* XXX arbitrary guesses */
+	connection_cost = 10.0;
+	transfer_cost = 1.0;
+	*startup_cost += connection_cost;
+	*total_cost += connection_cost;
+	*total_cost += transfer_cost * width * baserel->tuples;
+}
+
+/* ============================================================================
+ * Connection management functions
+ * ==========================================================================*/
+
+/*
+ * Connection cache entry managed with hash table.
+ */
+typedef struct ConnCacheEntry
+{
+	/* hash key must be first */
+	char			name[NAMEDATALEN];	/* connection name; used as hash key */
+	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;
+
+/*
+ * 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.
+ */
+static PGconn *
+GetConnection(ForeignServer *server, UserMapping *user)
+{
+	const char	   *conname = server->servername;
+	bool			found;
+	ConnCacheEntry *entry;
+	PGconn		   *conn = NULL;
+
+	/* initialize connection cache if it isn't */
+	if (FSConnectionHash == NULL)
+	{
+		HASHCTL		ctl;
+
+		/* hash key is the name of the connection */
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = NAMEDATALEN;
+		ctl.entrysize = sizeof(ConnCacheEntry);
+		/* allocate FSConnectionHash in the cache context */
+		ctl.hcxt = CacheMemoryContext;
+		FSConnectionHash = hash_create("Foreign Connections", 32,
+									   &ctl,
+									   HASH_ELEM | HASH_CONTEXT);
+	}
+
+	/* Is there any cached and valid connection with such name? */
+	entry = hash_search(FSConnectionHash, conname, HASH_ENTER, &found);
+	if (found)
+	{
+		if (entry->conn != NULL)
+		{
+			entry->refs++;
+			elog(DEBUG3, "ref %d for %s", entry->refs, entry->name);
+			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 ResourceOner to clean the connection up on error including
+		 * user interrupt.
+		 */
+		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: entry->name has been initialized in hash_search(HASH_ENTER).
+		 */
+		entry->refs = 1;
+		entry->conn = conn;
+		elog(DEBUG3, "connected to %s (%d)", entry->name, 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 int
+flatten_generic_options(List *defelems, const char **keywords, const char **values)
+{
+	ListCell *lc;
+	int i;
+
+	i = 0;
+	foreach(lc, defelems)
+	{
+		DefElem *d = (DefElem *) lfirst(lc);
+		keywords[i] = d->defname;
+		values[i] = strVal(d->arg);
+		i++;
+	}
+	return i;
+}
+
+static PGconn *
+connect_pg_server(ForeignServer *server, UserMapping *user)
+{
+	const char	   *conname = server->servername;
+	PGconn		   *conn;
+	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.  Generic options might not be a one of connection options.
+	 */
+	n = list_length(server->options) + list_length(user->options) + 1;
+	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 += flatten_generic_options(server->options,
+								 all_keywords + n, all_values + n);
+	n += flatten_generic_options(user->options,
+								 all_keywords + n, all_values + n);
+	all_keywords[n] = all_values[n] = NULL;
+
+	for (i = 0, j = 0; all_keywords[i]; i++)
+	{
+		/* Use only valid libpq connection options. */
+		if (!is_conninfo_option(all_keywords[i]))
+			continue;
+
+		keywords[j] = all_keywords[i];
+		values[j] = all_values[i];
+		j++;
+	}
+	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);
+
+	return conn;
+}
+
+#ifdef NOT_USED
+/*
+ * Mark the connection as "unused", and close it if the caller was the last
+ * user of the connection.
+ */
+static void
+ReleaseConnection(PGconn *conn)
+{
+	HASH_SEQ_STATUS		scan;
+	ConnCacheEntry	   *entry;
+
+	if (conn == NULL)
+		return;
+
+	/*
+	 * We need to scan seqencially 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;
+	}
+	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 referer, unregister it from cache. */
+	entry->refs--;
+	elog(DEBUG3, "ref %d for %s", entry->refs, entry->name);
+	if (entry->refs == 0)
+	{
+		elog(DEBUG3, "closing connection \"%s\"", entry->name);
+		PQfinish(entry->conn);
+		entry->refs = 0;
+		entry->conn = NULL;
+	}
+}
+#endif
+
+/*
+ * 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(DEBUG3, "closing connection to %s", entry->name);
+		PQfinish(entry->conn);
+		entry->refs = 0;
+		entry->conn = NULL;
+	}
+	else
+		elog(DEBUG3, "connection to %s already closed", entry->name);
+}
diff --git a/contrib/postgresql_fdw/postgresql_fdw.control b/contrib/postgresql_fdw/postgresql_fdw.control
new file mode 100644
index 0000000..ef78aaf
--- /dev/null
+++ b/contrib/postgresql_fdw/postgresql_fdw.control
@@ -0,0 +1,5 @@
+# postgresql_fdw extension
+comment = 'Foreign-data wrapepr for external PostgreSQL server'
+default_version = '1.0'
+module_pathname = '$libdir/postgresql_fdw'
+relocatable = true
diff --git a/contrib/postgresql_fdw/postgresql_fdw.h b/contrib/postgresql_fdw/postgresql_fdw.h
new file mode 100644
index 0000000..cc929df
--- /dev/null
+++ b/contrib/postgresql_fdw/postgresql_fdw.h
@@ -0,0 +1,18 @@
+/*
+ * postgresql_fdw.h
+ *
+ * Foreign-data wrapper handler for PostgreSQL
+ *
+ * contrib/postgresql_fdw/postgresql_fdw.h
+ * Copyright (c) 2011, PostgreSQL Global Development Group
+ * ALL RIGHTS RESERVED;
+ *
+ */
+
+#ifndef POSTGRESQL_FDW_H
+#define POSTGRESQL_FDW_H
+
+/* Connection name used for unnamed connection */
+#define UNNAMED_CONN_NAME	"unnamed"
+
+#endif   /* POSTGRESQL_FDW_H */
diff --git a/contrib/postgresql_fdw/sql/postgresql_fdw.sql b/contrib/postgresql_fdw/sql/postgresql_fdw.sql
new file mode 100644
index 0000000..eee9ff9
--- /dev/null
+++ b/contrib/postgresql_fdw/sql/postgresql_fdw.sql
@@ -0,0 +1,84 @@
+SET DATESTYLE = 'Postgres, MDY';
+
+-- =============================================================================
+-- Prepare section
+-- =============================================================================
+CREATE EXTENSION postgresql_fdw;
+
+-- connect database for regression test
+\c contrib_regression
+
+-- define fdw-related objects
+CREATE SERVER loopback1 FOREIGN DATA WRAPPER postgresql_fdw
+	OPTIONS (dbname 'contrib_regression');
+CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgresql_fdw
+	OPTIONS (dbname 'contrib_regression');
+
+CREATE USER MAPPING FOR PUBLIC SERVER loopback1;
+CREATE USER MAPPING FOR PUBLIC SERVER loopback2 OPTIONS (user 'invalid');
+
+CREATE TABLE t1(
+	c1 integer,
+	c2 text,
+	c3 date
+);
+
+COPY t1 FROM stdin;
+1	foo	1970-01-01
+2	bar	1970-01-02
+3	buz	1970-01-03
+\.
+
+CREATE TABLE t2(
+	c1 integer,
+	c2 text,
+	c3 date
+);
+
+COPY t2 FROM stdin;
+1	foo	1970-01-01
+12	bar	1970-01-02
+13	buz	1970-01-03
+\.
+
+CREATE FOREIGN TABLE ft1 (
+	c1 integer,
+	c2 text,
+	c3 date
+) SERVER loopback1 OPTIONS (relname 't1');
+
+CREATE FOREIGN TABLE ft2 (
+	c1 integer,
+	c2 text,
+	c3 date
+) SERVER loopback2 OPTIONS (relname 'invalid');
+
+-- simple query and connection caching
+SELECT ft1.* FROM ft1 ORDER BY c1;
+SELECT * FROM ft2 ORDER BY c1; -- ERROR
+ALTER USER MAPPING FOR PUBLIC SERVER loopback2 OPTIONS (DROP user);
+SELECT * FROM ft2 ORDER BY c1; -- ERROR
+ALTER FOREIGN TABLE ft2 OPTIONS (SET relname 't2');
+SELECT * FROM ft2 ORDER BY c1;
+
+-- query with projection
+SELECT c1 FROM ft1 ORDER BY c1;
+
+-- join two foreign tables
+SELECT * FROM ft1 JOIN ft2 ON (ft1.c1 = ft2.c1) ORDER BY ft1.c1;
+-- join itself
+SELECT * FROM ft1 t1 JOIN ft1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1;
+-- outer join
+SELECT * FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY 1,2,3,4,5,6;
+-- WHERE clause push-down
+SELECT * FROM ft1 WHERE c1 = 1 AND c2 = lower('FOO') AND c3 < now() and c3 < clock_timestamp();
+EXPLAIN (COSTS FALSE) SELECT * FROM ft1 WHERE c1 = 1 AND c2 = lower('FOO') AND c3 < now() and c3 < clock_timestamp();
+-- prepared statement
+PREPARE st(int) AS SELECT * FROM ft1 WHERE c1 > $1 ORDER BY c1;
+EXECUTE st(1);
+EXECUTE st(2);
+DEALLOCATE st;
+
+-- clean up
+DROP TABLE t1 CASCADE;
+DROP EXTENSION postgresql_fdw CASCADE;
diff --git a/contrib/postgresql_fdw/uninstall_postgresql_fdw.sql b/contrib/postgresql_fdw/uninstall_postgresql_fdw.sql
new file mode 100644
index 0000000..a24bbd4
--- /dev/null
+++ b/contrib/postgresql_fdw/uninstall_postgresql_fdw.sql
@@ -0,0 +1,7 @@
+/* contrib/postgresql_fdw/uninstall_postgresql_fdw.sql */
+
+-- Adjust this setting to control where the objects get dropped.
+set search_path = public;
+
+DROP FOREIGN DATA WRAPPER postgresql_fdw;
+DROP FUNCTION postgresql_fdw_handler ();
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index adf09ca..b60436c 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -123,6 +123,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
  &pgtestfsync;
  &pgtrgm;
  &pgupgrade;
+ &postgresql-fdw;
  &seg;
  &sepgsql;
  &contrib-spi;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 1a60796..e72809a 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -129,6 +129,7 @@
 <!entity pgtestfsync     SYSTEM "pgtestfsync.sgml">
 <!entity pgtrgm          SYSTEM "pgtrgm.sgml">
 <!entity pgupgrade       SYSTEM "pgupgrade.sgml">
+<!entity postgresql-fdw  SYSTEM "postgresql-fdw.sgml">
 <!entity seg             SYSTEM "seg.sgml">
 <!entity contrib-spi     SYSTEM "contrib-spi.sgml">
 <!entity sepgsql         SYSTEM "sepgsql.sgml">
diff --git a/doc/src/sgml/postgresql-fdw.sgml b/doc/src/sgml/postgresql-fdw.sgml
new file mode 100644
index 0000000..60dcdd0
--- /dev/null
+++ b/doc/src/sgml/postgresql-fdw.sgml
@@ -0,0 +1,166 @@
+<!-- doc/src/sgml/postgresql_fdw.sgml -->
+
+<sect1 id="postgresql-fdw">
+ <title>postgresql_fdw</title>
+
+ <indexterm zone="postgresql-fdw">
+  <primary>postgresql_fdw</primary>
+ </indexterm>
+
+ <para>
+  The <filename>postgresql_fdw</> module provides foreign-data wrapper
+  handler function <function>postgresql_fdw_handler</function> which can be
+  used to access external <productname>PostgreSQL</> server via plain SQL.
+ </para>
+
+ <sect2>
+  <title>Functions</title>
+
+  <variablelist>
+   <varlistentry>
+    <term>
+     <function>postgresql_fdw_handler() returns fdw_handler</function>
+    </term>
+
+    <listitem>
+     <para>
+      <function>postgresql_fdw_handler</function> is a foreign-data wrapper
+      handler function which returns foreign-data wrapper handler for
+      PostgreSQL in type of <type>fdw_handler</type>.
+      Since fdw_handler is a pseudo type, postgresql_fdw_handler can't be
+      called from a SQL statement.
+     </para>
+     <para>
+      Internally, it returns a pointer to a <structname>FdwRoutine</structname>
+      object which has set of foreign-data wrapper API functions for handling
+      foreign scan on the external PostgreSQL server.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+
+ </sect2>
+
+ <sect2>
+  <title>Details of postgresql_fdw</title>
+
+  <sect3>
+   <title>Object name options</title>
+   <para>
+    The postgresql_fdw accepts <literal>nspname</literal> and
+    <literal>rename</literal> options which can be used to specify object name
+    used in the remote query. The <literal>nspname</literal> is used as schema
+    name, and <literal>relname</literal> is used as relation name.
+    If these options are omitted, local catalog names are used.
+    These options can be specified for foreign tables.
+   </para>
+  </sect3>
+
+  <sect3>
+   <title>Connection options</title>
+   <para>
+    The postgresql_fdw accepts all libpq connection options. 
+    <literal>user</literal> and <literal>password</literal> can be specified
+    only for user mapping, and other options can be specified for only foreign
+    server.
+   </para>
+  </sect3>
+
+  <sect3>
+   <title>Connection management</title>
+   <para>
+    The postgresql_fdw connects to a remote PostgreSQL server when a scan on
+    the server is requested first time in the local query.
+    The connection is used by all of remote queries which are executed on same
+    remote PostgreSQL server.
+    If the local query uses multiple foreign PostgreSQL servers, connections
+    are established for each server (not for each foreign table) and all of
+    them will be closed at the end of the query.  This also means that
+    connection pooling is not implemented in postgresql_fdw.
+   </para>
+   <para>
+   </para>
+  </sect3>
+
+  <sect3>
+   <title>Transaction management</title>
+   <para>
+    The postgresql_fdw never emit transaction command such as <command>BEGIN</>,
+    <command>ROLLBACK</> and <command>COMMIT</>. Thus, all SQL statements are
+    executed in each transaction when '<varname>autocommit</>' was set to 'on'. 
+   </para>
+  </sect3>
+
+  <sect3>
+   <title>Retrieving all tuples at once</title>
+   <para>
+    The postgresql_fdw retrieves all of the result tuples at once via libpq
+    when the query was executed.  Note that huge result set causes huge memory
+    consumption.  The memory for the result set will be freed at the end of the
+    each query.
+   </para>
+  </sect3>
+
+  <sect3>
+   <title>WHERE clause push-down</title>
+   <para>
+    The postgresql_fdw pushes some part of WHERE clause down to the remote
+    server, only if the evaluating the part of clause doesn't break the
+    consistency of the query.  If a clause consist of elements below, the
+    clause will be pushed down.
+   </para>
+   <table id="postgresql-fdw-push-downable">
+    <title>push-down-able elements</title>
+    <tgroup cols="2">
+     <thead>
+      <row>
+      <entry>Element</entry>
+       <entry>Note</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry>Constant value and column reference</entry>
+      <entry></entry>
+      </row>
+      <row>
+       <entry>Array of push-down-able type</entry>
+       <entry></entry>
+      </row>
+      <row>
+       <entry>Parameter of <command>EXECUTE</command></entry>
+       <entry></entry>
+      </row>
+      <row>
+       <entry>Bool expression such as <literal>A AND B</literal> or
+       <literal>A OR B</literal></entry>
+       <entry></entry>
+      </row>
+      <row>
+       <entry>Non-volatile operator</entry>
+       <entry></entry>
+      </row>
+      <row>
+       <entry>DISTINCT operator, such as
+       <literal>A IS DISTINCT FROM B</literal></entry>
+       <entry></entry>
+      </row>
+      <row>
+       <entry>Scalar array operator, such as <literal>ALL(...)</literal> and
+       <literal>ANY(...)</literal></entry>
+       <entry></entry>
+      </row>
+      <row>
+       <entry>Non-volatile function call</entry>
+       <entry></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
+  </sect3>
+
+ </sect2>
+
+</sect1>
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 44cd181..a675155 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -444,10 +444,11 @@ pg_options_to_table(PG_FUNCTION_ARGS)
 /*
  * Describes the valid options for postgresql FDW, server, and user mapping.
  */
-struct ConnectionOption
+struct FdwOption
 {
-	const char *optname;
+	const char *optname;		/* name of the option */
 	Oid			optcontext;		/* Oid of catalog in which option may appear */
+	bool		is_conninfo;	/* T if the option is a libpq conninfo option */
 };
 
 /*
@@ -455,22 +456,26 @@ struct ConnectionOption
  *
  * The list is small - don't bother with bsearch if it stays so.
  */
-static struct ConnectionOption libpq_conninfo_options[] = {
-	{"authtype", ForeignServerRelationId},
-	{"service", ForeignServerRelationId},
-	{"user", UserMappingRelationId},
-	{"password", UserMappingRelationId},
-	{"connect_timeout", ForeignServerRelationId},
-	{"dbname", ForeignServerRelationId},
-	{"host", ForeignServerRelationId},
-	{"hostaddr", ForeignServerRelationId},
-	{"port", ForeignServerRelationId},
-	{"tty", ForeignServerRelationId},
-	{"options", ForeignServerRelationId},
-	{"requiressl", ForeignServerRelationId},
-	{"sslmode", ForeignServerRelationId},
-	{"gsslib", ForeignServerRelationId},
-	{NULL, InvalidOid}
+static struct FdwOption postgresql_fdw_options[] = {
+	/* libpq connection options */
+	{"authtype", ForeignServerRelationId, true},
+	{"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},
+	{"tty", ForeignServerRelationId, true},
+	{"options", ForeignServerRelationId, true},
+	{"requiressl", ForeignServerRelationId, true},
+	{"sslmode", ForeignServerRelationId, true},
+	{"gsslib", ForeignServerRelationId, true},
+	/* catalog options */
+	{"nspname", ForeignTableRelationId, false},
+	{"relname", ForeignTableRelationId, false},
+	{NULL, InvalidOid, false}
 };
 
 
@@ -479,12 +484,28 @@ static struct ConnectionOption libpq_conninfo_options[] = {
  * context is the Oid of the catalog the option came from, or 0 if we
  * don't care.
  */
+bool
+is_conninfo_option(const char *option)
+{
+	struct FdwOption *opt;
+
+	for (opt = postgresql_fdw_options; opt->optname; opt++)
+		if (strcmp(opt->optname, option) == 0)
+			return true;
+	return false;
+}
+
+/*
+ * Check if the provided option is one of postgresql_fdw options.
+ * context is the Oid of the catalog the option came from, or 0 if we
+ * don't care.
+ */
 static bool
-is_conninfo_option(const char *option, Oid context)
+is_postgresql_fdw_option(const char *option, Oid context)
 {
-	struct ConnectionOption *opt;
+	struct FdwOption *opt;
 
-	for (opt = libpq_conninfo_options; opt->optname; opt++)
+	for (opt = postgresql_fdw_options; opt->optname; opt++)
 		if (context == opt->optcontext && strcmp(opt->optname, option) == 0)
 			return true;
 	return false;
@@ -511,9 +532,9 @@ postgresql_fdw_validator(PG_FUNCTION_ARGS)
 	{
 		DefElem    *def = lfirst(cell);
 
-		if (!is_conninfo_option(def->defname, catalog))
+		if (!is_postgresql_fdw_option(def->defname, catalog))
 		{
-			struct ConnectionOption *opt;
+			struct FdwOption *opt;
 			StringInfoData buf;
 
 			/*
@@ -521,7 +542,7 @@ postgresql_fdw_validator(PG_FUNCTION_ARGS)
 			 * with list of valid options for the object.
 			 */
 			initStringInfo(&buf);
-			for (opt = libpq_conninfo_options; opt->optname; opt++)
+			for (opt = postgresql_fdw_options; opt->optname; opt++)
 				if (catalog == opt->optcontext)
 					appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
 									 opt->optname);
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index d676f3f..551bff6 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -77,5 +77,6 @@ extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
 							bool missing_ok);
 extern Oid	GetForeignDataWrapperOidByName(const char *name, bool missing_ok);
 extern ForeignTable *GetForeignTable(Oid relid);
+bool is_conninfo_option(const char *option);
 
 #endif   /* FOREIGN_H */