diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index be132f2..f4cb885 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2819,6 +2819,17 @@
      </row>
 
      <row>
+      <entry><structfield>fdwhandler</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+      <entry>
+       References a handler function that is responsible for
+       supplying foreign-data wrapper routines.
+       Zero if no handler is provided.
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>fdwacl</structfield></entry>
       <entry><type>aclitem[]</type></entry>
       <entry></entry>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index a65b4bc..06a82a4 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2986,6 +2986,41 @@ ANALYZE measurement;
   </sect2>
  </sect1>
 
+ <sect1 id="ddl-foreign-data">
+  <title>Foreign Data</title>
+   <indexterm>
+    <primary>foreign data</primary>
+   </indexterm>
+   <para>
+    <productname>PostgreSQL</productname> implements parts of the SQL/MED
+    specification, which allows you to access external data that resides
+    outside PostgreSQL, using normal SQL queries.
+   </para>
+
+   <para>
+    Foreign data is accessed with help from a
+    <firstterm>foreign data wrapper</firstterm>. A foreign data wrapper is a
+    library that can communicate with an external data source, hiding the
+    details of connecting to the data source and fetching data from it. There
+    are several foreign data wrappers available for e.g reading files residing
+    on the server, or to connect to another PostgreSQL instance. If none of
+    the existing foreign data wrappers suite your needs, you can write your
+    own, see <xref linkend="fdwhandler">.
+   </para>
+
+   <para>
+    To access foreign data, you need to create a foreign server to define
+    the connection details to the external data source, using options required
+    by the foreign data wrapper. Then you need to create one or more
+    <firstterm>foreign tables</firstterm> that define the structure of the
+    remote data. A foreign table can be used in queries just like a normal
+    table, but a foreign table has no storage in the PostgreSQL server.
+    Whenever it is used, PostgreSQL asks the foreign data wrapper to fetch
+    the data from the external source.
+   </para>
+
+ </sect1>
+
  <sect1 id="ddl-others">
   <title>Other Database Objects</title>
 
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
new file mode 100644
index 0000000..8ef93bc
--- /dev/null
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -0,0 +1,125 @@
+<!-- doc/src/sgml/fdwhandler.sgml -->
+
+ <chapter id="fdwhandler">
+   <title>Writing A Foreign Data Wrapper</title>
+
+   <indexterm zone="fdwhandler">
+    <primary>foreign data wrapper</primary>
+    <secondary>handler for</secondary>
+   </indexterm>
+
+   <para>
+    All operations on foreign tables are handled through a foreign data
+    wrapper, which consists of a set of functions that the planner and
+    executor call. The foreign data wrapper is responsible for fetching
+    data from the remote data source and returning it to the PostgreSQL
+    executor. This chapter outlines how to write a new foreign data wrapper.
+   </para>
+
+   <para>
+    The FDW provider needs to implement a handler function, and (optionally)
+    a validator function. Both functions must be written in a compiled
+    language such as C, using the version-1 interface.
+
+    The validator function must be registered with
+    <productname>PostgreSQL</productname> as taking two arguments, a text
+    array containing the options, and an oid representing the type of object
+    the options are to be associated with. The handler function must be
+    registered as taking no arguments, and returning the type
+    <type>fdw_handler</type>.  This special pseudotype identifies the
+    function as a fdw handler and prevents it from being called directly in
+    SQL commands. For more details on C language calling conventions and
+    dynamic loading, see <xref linkend="xfunc-c">.
+   </para>
+
+   <para>
+    The validator function is responsible for validating
+    options given in the CREATE FOREIGN DATA WRAPPER, CREATE SERVER and
+    CREATE FOREIGN TABLE commands.
+   </para>
+
+   <para>
+    The handler function returns a struct of function pointers to callback
+    functions that are called by the planner and executor.
+   </para>
+
+   <para>
+    The SQL standard specifies an interface for writing foreign data wrappers.
+    However, PostgreSQL does not implement that API, because the effort to
+    accommodate it into PostgreSQL would be large. The standard API hasn't
+    gained wide adoption anyway, there aren't many standard-compliant foreign
+    data wrappers out there.
+   </para>
+
+   <para>
+    The foreign data wrappers included in the standard distribution
+    are good references when trying to write your own.
+    Look into the <filename>contrib/file_fdw</> subdirectory of the source tree.
+    The <xref linkend="sql-createforeigndatawrapper">
+    reference page also has some useful details.
+   </para>
+
+   <sect1 id="fdw-routines">
+    <title>Foreign Data Wrapper routines</title>
+
+    <para>
+     The FDW handler function returns a FdwRoutine struct with the following
+     callback functions:
+    </para>
+
+    <para>
+<programlisting>
+FdwPlan *
+PlanRelScan (Oid foreigntableid,
+             PlannerInfo *root,
+             RelOptInfo *baserel);
+</programlisting>
+   Plan a scan on a foreign table. This is called when a query is planned.
+   The function must return a palloc'd struct containing cost estimates,
+   a string to show for this scan in EXPLAIN, and any FDW-private information
+   that is needed to execute the foreign scan at a later stage.
+   A prepared plan may be executed many times.
+    </para>
+
+    <para>
+<programlisting>
+FdwExecutionState *
+BeginScan (FdwPlan *plan
+           ParamListInfo *params);
+</programlisting>
+   Begin exeucuting a foreign scan. This is called when a query is executed.
+   The function must return a palloc'd struct containing any private
+   information needed by the Iterate and EndScan calls that follow.
+    </para>
+
+    <para>
+<programlisting>
+void
+Iterate (FdwExecutionState *state,
+         TupleTableSlot *slot);
+</programlisting>
+   Fetch one row from the foreign source. Note that this is called in a
+   short-lived memory context that may be reset between every invocation of
+   Iterate. Create a memory context in BeginScan if you need longer-lived
+   storage.
+    </para>
+
+    <para>
+<programlisting>
+void
+ReScan (FdwExecutionState *state);
+</programlisting>
+   Restarts the scan.
+    </para>
+
+    <para>
+<programlisting>
+void
+EndScan (FdwExecutionState *state);
+</programlisting>
+   Ends the scan.
+    </para>
+
+   </sect1>
+
+ </chapter>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index b9d4ea5..659bcba 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -86,6 +86,7 @@
 <!entity indexam    SYSTEM "indexam.sgml">
 <!entity nls        SYSTEM "nls.sgml">
 <!entity plhandler  SYSTEM "plhandler.sgml">
+<!entity fdwhandler SYSTEM "fdwhandler.sgml">
 <!entity protocol   SYSTEM "protocol.sgml">
 <!entity sources    SYSTEM "sources.sgml">
 <!entity storage    SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 4d32f7d..98d19a5 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -238,6 +238,7 @@
   &sources;
   &nls;
   &plhandler;
+  &fdwhandler;
   &geqo;
   &indexam;
   &gist;
diff --git a/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml b/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml
index 4e9e8a2..7e17ef3 100644
--- a/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml
+++ b/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml
@@ -23,6 +23,7 @@ PostgreSQL documentation
 <synopsis>
 ALTER FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable>
     [ VALIDATOR <replaceable class="parameter">valfunction</replaceable> | NO VALIDATOR ]
+    [ HANDLER <replaceable class="parameter">handler</replaceable> | NO HANDLER ]
     [ OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ]) ]
 ALTER FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable> OWNER TO <replaceable>new_owner</replaceable>
 </synopsis>
@@ -86,6 +87,29 @@ ALTER FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable> OWN
    </varlistentry>
 
    <varlistentry>
+    <term><literal>HANDLER <replaceable class="parameter">handler</replaceable></literal></term>
+    <listitem>
+     <para>
+      Specifies a new foreign-data wrapper handler function.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>NO HANDLER</literal></term>
+    <listitem>
+     <para>
+      This is used to specify that the foreign-data wrapper should no
+      longer have a handler function.
+     </para>
+     <para>
+      Note that foreign tables which uses a foreign-data wrapper with no
+      handler can't be used in a SELECT statement.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
     <listitem>
      <para>
@@ -127,8 +151,8 @@ ALTER FOREIGN DATA WRAPPER dbi VALIDATOR bob.myvalidator;
   <para>
    <command>ALTER FOREIGN DATA WRAPPER</command> conforms to ISO/IEC
    9075-9 (SQL/MED).  The standard does not specify the <literal>
-   VALIDATOR</literal> and <literal>OWNER TO</> variants of the
-   command.
+   VALIDATOR</literal>, <literal>HANDLER</> and <literal>OWNER TO</>
+   variants of the command.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/create_foreign_data_wrapper.sgml b/doc/src/sgml/ref/create_foreign_data_wrapper.sgml
index f626d56..d70321b 100644
--- a/doc/src/sgml/ref/create_foreign_data_wrapper.sgml
+++ b/doc/src/sgml/ref/create_foreign_data_wrapper.sgml
@@ -23,6 +23,7 @@ PostgreSQL documentation
 <synopsis>
 CREATE FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable>
     [ VALIDATOR <replaceable class="parameter">valfunction</replaceable> | NO VALIDATOR ]
+    [ HANDLER <replaceable class="parameter">handler</replaceable> | NO HANDLER ]
     [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
 </synopsis>
  </refsynopsisdiv>
@@ -82,6 +83,19 @@ CREATE FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>HANDLER <replaceable class="parameter">handler</replaceable></literal></term>
+    <listitem>
+     <para>
+      <replaceable class="parameter">handler</replaceable> is the
+      name of a previously registered function that will be called to
+      retrieve a set of functions for foreign tables.
+      The handler function must take no arguments.
+      The return type must be <type>fdw_handler</type>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] )</literal></term>
     <listitem>
      <para>
@@ -151,8 +165,8 @@ CREATE FOREIGN DATA WRAPPER mywrapper
 
   <para>
    <command>CREATE FOREIGN DATA WRAPPER</command> conforms to ISO/IEC
-   9075-9 (SQL/MED), with the exception that
-   the <literal>VALIDATOR</literal> clause is an extension and the
+   9075-9 (SQL/MED), with the exception that the <literal>VALIDATOR</literal>
+   and <literal>HANDLER</literal> clauses are extensions and the
    clauses <literal>LIBRARY</literal> and <literal>LANGUAGE</literal>
    are not yet implemented in PostgreSQL.
   </para>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 6534590..3f003c8 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -705,6 +705,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_WorkTableScan:
 			pname = sname = "WorkTable Scan";
 			break;
+		case T_ForeignScan:
+			pname = sname = "Foreign Scan";
+			break;
 		case T_Material:
 			pname = sname = "Materialize";
 			break;
@@ -854,6 +857,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ValuesScan:
 		case T_CteScan:
 		case T_WorkTableScan:
+		case T_ForeignScan:
 			ExplainScanTarget((Scan *) plan, es);
 			break;
 		case T_BitmapIndexScan:
@@ -1033,6 +1037,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ValuesScan:
 		case T_CteScan:
 		case T_WorkTableScan:
+		case T_ForeignScan:
 		case T_SubqueryScan:
 			show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
 			break;
@@ -1100,6 +1105,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			break;
 	}
 
+	/* Show FDW specific information, if any */
+	if (IsA(plan, ForeignScan))
+	{
+		ForeignScan *scan = (ForeignScan *) plan;
+		if (scan->fplan->explainInfo)
+			ExplainPropertyText("FDW-Info", scan->fplan->explainInfo, es);
+	}
+
 	/* Show buffer usage */
 	if (es->buffers)
 	{
@@ -1570,6 +1583,7 @@ ExplainScanTarget(Scan *plan, ExplainState *es)
 		case T_IndexScan:
 		case T_BitmapHeapScan:
 		case T_TidScan:
+		case T_ForeignScan:
 			/* Assert it's on a real relation */
 			Assert(rte->rtekind == RTE_RELATION);
 			objectname = get_rel_name(rte->relid);
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index 3a0ea9a..fc1d9b4 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -317,16 +317,69 @@ AlterForeignServerOwner(const char *name, Oid newOwnerId)
  * Convert a validator function name passed from the parser to an Oid.
  */
 static Oid
-lookup_fdw_validator_func(List *validator)
+lookup_fdw_validator_func(DefElem *validator)
 {
 	Oid			funcargtypes[2];
 
+	if (validator == NULL || validator->arg == NULL)
+		return InvalidOid;
+
 	funcargtypes[0] = TEXTARRAYOID;
 	funcargtypes[1] = OIDOID;
-	return LookupFuncName(validator, 2, funcargtypes, false);
+	return LookupFuncName((List *) validator->arg, 2, funcargtypes, false);
 	/* return value is ignored, so we don't check the type */
 }
 
+static Oid
+lookup_fdw_handler_func(DefElem *handler)
+{
+	Oid			handlerOid;
+
+	if (handler == NULL || handler->arg == NULL)
+		return InvalidOid;
+
+	/* check that handler have correct return type */
+	handlerOid = LookupFuncName((List *) handler->arg, 0, NULL, false);
+	if (get_func_rettype(handlerOid) != FDW_HANDLEROID)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("function %s must return type \"fdw_handler\"",
+						NameListToString((List *) handler->arg))));
+	}
+
+	return handlerOid;
+}
+
+static void
+parse_func_options(List *func_options, DefElem **validator, DefElem **handler)
+{
+	ListCell   *cell;
+
+	*validator = NULL;
+	*handler = NULL;
+	foreach(cell, func_options)
+	{
+		DefElem    *def = lfirst(cell);
+
+		if (pg_strcasecmp(def->defname, "validator") == 0)
+		{
+			if (*validator)
+				ereport(ERROR, (errmsg("duplicated VALIDATOR")));
+			*validator = def;
+		}
+		else if (pg_strcasecmp(def->defname, "handler") == 0)
+		{
+			if (*handler)
+				ereport(ERROR, (errmsg("duplicated HANDLER")));
+			*handler = def;
+		}
+		else
+		{
+			ereport(ERROR, (errmsg("invalid option")));
+		}
+	}
+}
 
 /*
  * Create a foreign-data wrapper
@@ -339,7 +392,10 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt)
 	bool		nulls[Natts_pg_foreign_data_wrapper];
 	HeapTuple	tuple;
 	Oid			fdwId;
+	DefElem    *defvalidator;
+	DefElem    *defhandler;
 	Oid			fdwvalidator;
+	Oid			fdwhandler;
 	Datum		fdwoptions;
 	Oid			ownerId;
 
@@ -375,12 +431,13 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt)
 		DirectFunctionCall1(namein, CStringGetDatum(stmt->fdwname));
 	values[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(ownerId);
 
-	if (stmt->validator)
-		fdwvalidator = lookup_fdw_validator_func(stmt->validator);
-	else
-		fdwvalidator = InvalidOid;
+	/* determine which validator to be used (or not used at all) */
+	parse_func_options(stmt->func_options, &defvalidator, &defhandler);
+	fdwvalidator = lookup_fdw_validator_func(defvalidator);
+	fdwhandler = lookup_fdw_handler_func(defhandler);
 
 	values[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = fdwvalidator;
+	values[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = fdwhandler;
 
 	nulls[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true;
 
@@ -416,6 +473,21 @@ CreateForeignDataWrapper(CreateFdwStmt *stmt)
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
+	if (fdwhandler != InvalidOid)
+	{
+		ObjectAddress myself;
+		ObjectAddress referenced;
+
+		myself.classId = ForeignDataWrapperRelationId;
+		myself.objectId = fdwId;
+		myself.objectSubId = 0;
+
+		referenced.classId = ProcedureRelationId;
+		referenced.objectId = fdwhandler;
+		referenced.objectSubId = 0;
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
 	recordDependencyOnOwner(ForeignDataWrapperRelationId, fdwId, ownerId);
 
 	/* Post creation hook for new foreign data wrapper */
@@ -440,7 +512,10 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
 	Oid			fdwId;
 	bool		isnull;
 	Datum		datum;
+	DefElem    *defvalidator;
+	DefElem    *defhandler;
 	Oid			fdwvalidator;
+	Oid			fdwhandler;
 
 	/* Must be super user */
 	if (!superuser())
@@ -464,9 +539,11 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
 	memset(repl_null, false, sizeof(repl_null));
 	memset(repl_repl, false, sizeof(repl_repl));
 
-	if (stmt->change_validator)
+	parse_func_options(stmt->func_options, &defvalidator, &defhandler);
+
+	if (defvalidator)
 	{
-		fdwvalidator = stmt->validator ? lookup_fdw_validator_func(stmt->validator) : InvalidOid;
+		fdwvalidator = lookup_fdw_validator_func(defvalidator);
 		repl_val[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = ObjectIdGetDatum(fdwvalidator);
 		repl_repl[Anum_pg_foreign_data_wrapper_fdwvalidator - 1] = true;
 
@@ -474,7 +551,7 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
 		 * It could be that the options for the FDW, SERVER and USER MAPPING
 		 * are no longer valid with the new validator.	Warn about this.
 		 */
-		if (stmt->validator)
+		if (defvalidator->arg)
 			ereport(WARNING,
 			 (errmsg("changing the foreign-data wrapper validator can cause "
 					 "the options for dependent objects to become invalid")));
@@ -492,6 +569,33 @@ AlterForeignDataWrapper(AlterFdwStmt *stmt)
 		fdwvalidator = DatumGetObjectId(datum);
 	}
 
+	if (defhandler)
+	{
+		fdwhandler = lookup_fdw_handler_func(defhandler);
+		repl_val[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = ObjectIdGetDatum(fdwhandler);
+		repl_repl[Anum_pg_foreign_data_wrapper_fdwhandler - 1] = true;
+
+		/*
+		 * It could be that the behavior of accessing foreign table changes
+		 * with the new handler.  Warn about this.
+		 */
+		if (defhandler->arg)
+			ereport(WARNING,
+					(errmsg("changing the foreign-data wrapper handler can change behavior of existing foreign tables")));
+	}
+	else
+	{
+		/*
+		 * Validator is not changed, but we need it for validating options.
+		 */
+		datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
+								tp,
+								Anum_pg_foreign_data_wrapper_fdwhandler,
+								&isnull);
+		Assert(!isnull);
+		fdwhandler = DatumGetObjectId(datum);
+	}
+
 	/*
 	 * Options specified, validate and update.
 	 */
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index da4fbb4..a854c9a 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -23,6 +23,6 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
        nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
        nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
        nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
-       nodeWindowAgg.o tstoreReceiver.o spi.o
+       nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o spi.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 6bdc60c..9f7b138 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -21,6 +21,7 @@
 #include "executor/nodeBitmapIndexscan.h"
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
+#include "executor/nodeForeignscan.h"
 #include "executor/nodeFunctionscan.h"
 #include "executor/nodeGroup.h"
 #include "executor/nodeGroup.h"
@@ -186,6 +187,10 @@ ExecReScan(PlanState *node)
 			ExecReScanWorkTableScan((WorkTableScanState *) node);
 			break;
 
+		case T_ForeignScanState:
+			ExecForeignReScan((ForeignScanState *) node);
+			break;
+
 		case T_NestLoopState:
 			ExecReScanNestLoop((NestLoopState *) node);
 			break;
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index e8ebec1..92b83ac 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -86,6 +86,7 @@
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
 #include "executor/nodeFunctionscan.h"
+#include "executor/nodeForeignscan.h"
 #include "executor/nodeGroup.h"
 #include "executor/nodeHash.h"
 #include "executor/nodeHashjoin.h"
@@ -232,6 +233,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 														 estate, eflags);
 			break;
 
+		case T_ForeignScan:
+			result = (PlanState *) ExecInitForeignScan((ForeignScan *) node,
+														 estate, eflags);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -422,6 +428,10 @@ ExecProcNode(PlanState *node)
 			result = ExecWorkTableScan((WorkTableScanState *) node);
 			break;
 
+		case T_ForeignScanState:
+			result = ExecForeignScan((ForeignScanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -650,6 +660,10 @@ ExecEndNode(PlanState *node)
 			ExecEndWorkTableScan((WorkTableScanState *) node);
 			break;
 
+		case T_ForeignScanState:
+			ExecEndForeignScan((ForeignScanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
new file mode 100644
index 0000000..d512b49
--- /dev/null
+++ b/src/backend/executor/nodeForeignscan.c
@@ -0,0 +1,286 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeForeignscan.c
+ *	  Support routines for scans of foreign tables.
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/nodeForeignscan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+/*
+ * INTERFACE ROUTINES
+ *		ExecForeignScan				scans a foreign table.
+ *		ExecInitForeignScan			creates and initializes a foreignscan node.
+ *		ExecEndForeignScan			releases any resources allocated.
+ */
+#include "postgres.h"
+
+#include "executor/executor.h"
+#include "executor/nodeForeignscan.h"
+#include "foreign/foreign.h"
+#include "miscadmin.h"
+#include "utils/memutils.h"
+
+static TupleTableSlot *ForeignNext(ForeignScanState *node);
+static bool ForeignRecheck(ForeignScanState *node, TupleTableSlot *slot);
+
+/* ----------------------------------------------------------------
+ *						Scan Support
+ * ----------------------------------------------------------------
+ */
+
+/* ----------------------------------------------------------------
+ *		ForeignNext
+ *
+ *		This is a workhorse for ExecForeignScan
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot *
+ForeignNext(ForeignScanState *node)
+{
+	TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+	ExprContext *econtext;
+	MemoryContext oldcontext;
+
+	Assert(node->ss.ps.state->es_direction == ForwardScanDirection);
+
+	/* tupleslot will be filled by Iterate. */
+	if (node->routine->Iterate == NULL)
+		ereport(ERROR,
+				(errmsg("foreign-data wrapper must support Iterate to scan foreign table")));
+
+	/* Slot has to be cleared explicitly before resetting per-tuple context. */
+	ExecClearTuple(slot);
+
+	/* We call Iterate in per-tuple context, similar to FunctionScan */
+	econtext = node->ss.ps.ps_ExprContext;
+	ResetExprContext(econtext);
+	oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+	node->routine->Iterate(node->fstate, slot);
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Set tableoid if the tuple was valid. */
+	if (!TupIsNull(slot))
+	{
+		/*
+		 * If the foreign-data wrapper returned a MinimalTuple, materialize
+		 * the tuple to store system attributes.
+		 */
+		if (!TTS_HAS_PHYSICAL_TUPLE(slot))
+			ExecMaterializeSlot(slot);
+
+		/* overwrite only tableoid of the tuple */
+		slot->tts_tuple->t_tableOid =
+			RelationGetRelid(node->ss.ss_currentRelation);
+	}
+
+	return slot;
+}
+
+/*
+ * ForeignRecheck -- access method routine to recheck a tuple in EvalPlanQual
+ */
+static bool
+ForeignRecheck(ForeignScanState *node, TupleTableSlot *slot)
+{
+	/* ForeignScan never use keys in ForeignNext. */
+	return true;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecForeignScan(node)
+ *
+ *		Fetches the next tuple from the FDW, checks local quals, and
+ *		returns it.
+ *		We call the ExecScan() routine and pass it the appropriate
+ *		access method functions.
+ * ----------------------------------------------------------------
+ */
+TupleTableSlot *
+ExecForeignScan(ForeignScanState *node)
+{
+	return ExecScan((ScanState *) node,
+					(ExecScanAccessMtd) ForeignNext,
+					(ExecScanRecheckMtd) ForeignRecheck);
+}
+
+
+/* ----------------------------------------------------------------
+ *		ExecInitForeignScan
+ * ----------------------------------------------------------------
+ */
+ForeignScanState *
+ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
+{
+	ForeignScanState *scanstate;
+	Relation	currentRelation;
+	FdwRoutine *routine;
+
+	/*
+	 * foreign scan has no child node.
+	 */
+	Assert(outerPlan(node) == NULL);
+	Assert(innerPlan(node) == NULL);
+
+	/*
+	 * create state structure
+	 */
+	scanstate = makeNode(ForeignScanState);
+	scanstate->ss.ps.plan = (Plan *) node;
+	scanstate->ss.ps.state = estate;
+
+	/*
+	 * Miscellaneous initialization
+	 *
+	 * create expression context for node
+	 */
+	ExecAssignExprContext(estate, &scanstate->ss.ps);
+
+	/*
+	 * initialize child expressions
+	 */
+	scanstate->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) node->scan.plan.targetlist,
+					 (PlanState *) scanstate);
+	scanstate->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) node->scan.plan.qual,
+					 (PlanState *) scanstate);
+
+	/*
+	 * tuple table initialization
+	 */
+	ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
+	ExecInitScanTupleSlot(estate, &scanstate->ss);
+
+	/*
+	 * initialize scan relation. get the relation object id from the relid'th
+	 * entry in the range table, open that relation and acquire appropriate
+	 * lock on it.
+	 */
+	currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid);
+	scanstate->ss.ss_currentRelation = currentRelation;
+	ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+	scanstate->ss.ps.ps_TupFromTlist = false;
+
+	/*
+	 * Initialize result tuple type and projection info.
+	 */
+	ExecAssignResultTypeFromTL(&scanstate->ss.ps);
+	ExecAssignScanProjectionInfo(&scanstate->ss);
+
+	/* cache the routine for the table in ForeignScanState */
+	routine = GetFdwRoutineByRelId(RelationGetRelid(currentRelation));
+	scanstate->routine = routine;
+
+	/*
+	 * If this execution was not for EXPLAIN w/o ANALYZE flag, initiate the
+	 * foreign scan.
+	 */
+	if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
+	{
+		ForeignScan *scan = (ForeignScan *) scanstate->ss.ps.plan;
+
+		if (routine->BeginScan == NULL)
+			ereport(ERROR,
+					(errmsg("foreign-data wrapper must support BeginScan to scan foreign table")));
+		scanstate->fstate = routine->BeginScan(scan->fplan,
+											   estate->es_param_list_info);
+	}
+
+	return scanstate;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecEndForeignScan
+ *
+ *		frees any storage allocated through C routines.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndForeignScan(ForeignScanState *node)
+{
+	Relation	relation;
+
+	/* close the scan, if it has been opened */
+	if (node->fstate != NULL)
+	{
+		if (node->routine->EndScan == NULL)
+			ereport(ERROR,
+					(errmsg("foreign-data wrapper must support BeginScan to scan foreign table")));
+		node->routine->EndScan(node->fstate);
+	}
+
+	/* get information from node */
+	relation = node->ss.ss_currentRelation;
+
+	/* Free the exprcontext */
+	ExecFreeExprContext(&node->ss.ps);
+
+	/* clean out the tuple table */
+	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+	ExecClearTuple(node->ss.ss_ScanTupleSlot);
+
+	/* close the relation. */
+	ExecCloseScanRelation(relation);
+}
+
+/* ----------------------------------------------------------------
+ *						Join Support
+ * ----------------------------------------------------------------
+ */
+
+/* ----------------------------------------------------------------
+ *		ExecForeignReScan
+ *
+ *		Rescans the relation.
+ * ----------------------------------------------------------------
+ */
+void
+ExecForeignReScan(ForeignScanState *node)
+{
+	/* Use EndScan and BeginScan if the FDW doesn't supply ReScan */
+	if (node->routine->ReScan == NULL)
+	{
+		ForeignScan *scan = (ForeignScan *) node->ss.ps.plan;
+		EState	   *estate = node->ss.ps.state;
+
+		Assert(node->routine->BeginScan);
+
+		node->routine->EndScan(node->fstate);
+		node->fstate = node->routine->BeginScan(scan->fplan,
+												estate->es_param_list_info);
+	}
+	else
+		node->routine->ReScan(node->fstate);
+
+	ExecScanReScan((ScanState *) node);
+}
+
+/* ----------------------------------------------------------------
+ *		ExecForeignMarkPos(node)
+ *
+ *		Marks scan position.
+ * ----------------------------------------------------------------
+ */
+void
+ExecForeignMarkPos(ForeignScanState *node)
+{
+	elog(ERROR, "ForeignScan does not support mark/restore");
+}
+
+/* ----------------------------------------------------------------
+ *		ExecForeignRestrPos
+ *
+ *		Restores scan position.
+ * ----------------------------------------------------------------
+ */
+void
+ExecForeignRestrPos(ForeignScanState *node)
+{
+	elog(ERROR, "ForeignScan does not support mark/restore");
+}
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 2e08008..e7ce86c 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -79,6 +79,12 @@ lnext:
 		if (node->lr_epqstate.estate != NULL)
 			EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, NULL);
 
+		/* if foreign table, the tuple can't be locked */
+		if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 (errmsg("SELECT FOR UPDATE/SHARE cannot be used with foreign tables"))));
+
 		/* if child rel, must check whether it produced this row */
 		if (erm->rti != erm->prti)
 		{
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 9a0f847..8089fc6 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -16,6 +16,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_foreign_data_wrapper.h"
 #include "catalog/pg_foreign_server.h"
+#include "catalog/pg_foreign_table.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
 #include "foreign/foreign.h"
@@ -59,6 +60,7 @@ GetForeignDataWrapper(Oid fdwid)
 	fdw->owner = fdwform->fdwowner;
 	fdw->fdwname = pstrdup(NameStr(fdwform->fdwname));
 	fdw->fdwvalidator = fdwform->fdwvalidator;
+	fdw->fdwhandler = fdwform->fdwhandler;
 
 	/* Extract the options */
 	datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
@@ -250,6 +252,116 @@ GetUserMapping(Oid userid, Oid serverid)
 
 
 /*
+ * GetForeignTable - look up the foreign table definition by relation oid.
+ */
+ForeignTable *
+GetForeignTable(Oid relid)
+{
+	Form_pg_foreign_table tableform;
+	ForeignTable *ft;
+	HeapTuple	tp;
+	Datum		datum;
+	bool		isnull;
+
+	tp = SearchSysCache(FOREIGNTABLEREL,
+						ObjectIdGetDatum(relid),
+						0, 0, 0);
+
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for foreign table %u", relid);
+
+	tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
+
+	ft = palloc(sizeof(ForeignTable));
+	ft->relid = relid;
+	ft->serverid = tableform->ftserver;
+
+	/* Extract the ftoptions */
+	datum = SysCacheGetAttr(FOREIGNTABLEREL,
+							tp,
+							Anum_pg_foreign_table_ftoptions,
+							&isnull);
+
+	/* untransformRelOptions does exactly what we want - avoid duplication */
+	ft->options = untransformRelOptions(datum);
+	ReleaseSysCache(tp);
+
+	return ft;
+}
+
+
+/*
+ * GetFdwRoutine - look up the handler of the foreign-data wrapper by OID and
+ * retrieve FdwRoutine.
+ */
+FdwRoutine *
+GetFdwRoutine(Oid fdwhandler)
+{
+	FmgrInfo	flinfo;
+	FunctionCallInfoData fcinfo;
+	Datum		result;
+	FdwRoutine *routine;
+
+	if (fdwhandler == InvalidOid)
+		elog(ERROR, "foreign-data wrapper has no handler");
+
+	fmgr_info(fdwhandler, &flinfo);
+	InitFunctionCallInfoData(fcinfo, &flinfo, 0, NULL, NULL);
+	result = FunctionCallInvoke(&fcinfo);
+
+	if (fcinfo.isnull ||
+		(routine = (FdwRoutine *) DatumGetPointer(result)) == NULL)
+	{
+		elog(ERROR, "function %u returned NULL", flinfo.fn_oid);
+		routine = NULL;			/* keep compiler quiet */
+	}
+
+	return routine;
+}
+
+
+/*
+ * GetFdwRoutineByRelId - look up the handler of the foreign-data wrapper by
+ * OID of the foreign table and retrieve FdwRoutine.
+ */
+FdwRoutine *
+GetFdwRoutineByRelId(Oid relid)
+{
+	HeapTuple	tp;
+	Form_pg_foreign_data_wrapper fdwform;
+	Form_pg_foreign_server serverform;
+	Form_pg_foreign_table tableform;
+	Oid			serverid;
+	Oid			fdwid;
+	Oid			fdwhandler;
+
+	/* Get function OID for the foreign table. */
+	tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for foreign table %u", relid);
+	tableform = (Form_pg_foreign_table) GETSTRUCT(tp);
+	serverid = tableform->ftserver;
+	ReleaseSysCache(tp);
+
+	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+	serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
+	fdwid = serverform->srvfdw;
+	ReleaseSysCache(tp);
+
+	tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+	fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
+	fdwhandler = fdwform->fdwhandler;
+	ReleaseSysCache(tp);
+
+	return GetFdwRoutine(fdwhandler);
+}
+
+
+/*
  * deflist_to_tuplestore - Helper function to convert DefElem list to
  * tuplestore usable in SRF.
  */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 662916d..2c5bb10 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -548,6 +548,44 @@ _copyWorkTableScan(WorkTableScan *from)
 }
 
 /*
+ * _copyForeignScan
+ */
+static ForeignScan *
+_copyForeignScan(ForeignScan *from)
+{
+	ForeignScan *newnode = makeNode(ForeignScan);
+
+	/*
+	 * copy node superclass fields
+	 */
+	CopyScanFields((Scan *) from, (Scan *) newnode);
+	COPY_NODE_FIELD(fplan);
+
+	return newnode;
+}
+
+/*
+ * _copyFdwPlan
+ */
+static FdwPlan *
+_copyFdwPlan(FdwPlan *from)
+{
+	FdwPlan *newnode = makeNode(FdwPlan);
+
+	/*
+	 * copy node superclass fields
+	 */
+	COPY_SCALAR_FIELD(serverid);
+	COPY_SCALAR_FIELD(userid);
+	COPY_STRING_FIELD(explainInfo);
+	COPY_SCALAR_FIELD(startup_cost);
+	COPY_SCALAR_FIELD(total_cost);
+	COPY_NODE_FIELD(fdw_private);
+
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -3202,7 +3240,7 @@ _copyCreateFdwStmt(CreateFdwStmt *from)
 	CreateFdwStmt *newnode = makeNode(CreateFdwStmt);
 
 	COPY_STRING_FIELD(fdwname);
-	COPY_NODE_FIELD(validator);
+	COPY_NODE_FIELD(func_options);
 	COPY_NODE_FIELD(options);
 
 	return newnode;
@@ -3214,8 +3252,7 @@ _copyAlterFdwStmt(AlterFdwStmt *from)
 	AlterFdwStmt *newnode = makeNode(AlterFdwStmt);
 
 	COPY_STRING_FIELD(fdwname);
-	COPY_NODE_FIELD(validator);
-	COPY_SCALAR_FIELD(change_validator);
+	COPY_NODE_FIELD(func_options);
 	COPY_NODE_FIELD(options);
 
 	return newnode;
@@ -3760,6 +3797,12 @@ copyObject(void *from)
 		case T_WorkTableScan:
 			retval = _copyWorkTableScan(from);
 			break;
+		case T_ForeignScan:
+			retval = _copyForeignScan(from);
+			break;
+		case T_FdwPlan:
+			retval = _copyFdwPlan(from);
+			break;
 		case T_Join:
 			retval = _copyJoin(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b7dc450..26df9ac 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1629,7 +1629,7 @@ static bool
 _equalCreateFdwStmt(CreateFdwStmt *a, CreateFdwStmt *b)
 {
 	COMPARE_STRING_FIELD(fdwname);
-	COMPARE_NODE_FIELD(validator);
+	COMPARE_NODE_FIELD(func_options);
 	COMPARE_NODE_FIELD(options);
 
 	return true;
@@ -1639,8 +1639,7 @@ static bool
 _equalAlterFdwStmt(AlterFdwStmt *a, AlterFdwStmt *b)
 {
 	COMPARE_STRING_FIELD(fdwname);
-	COMPARE_NODE_FIELD(validator);
-	COMPARE_SCALAR_FIELD(change_validator);
+	COMPARE_NODE_FIELD(func_options);
 	COMPARE_NODE_FIELD(options);
 
 	return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index c8eccce..30dc6da 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -533,6 +533,27 @@ _outWorkTableScan(StringInfo str, WorkTableScan *node)
 }
 
 static void
+_outForeignScan(StringInfo str, ForeignScan *node)
+{
+	WRITE_NODE_TYPE("FOREIGNSCAN");
+
+	_outScanInfo(str, (Scan *) node);
+}
+
+static void
+_outFdwPlan(StringInfo str, FdwPlan *node)
+{
+	WRITE_NODE_TYPE("FDWPLAN");
+
+	WRITE_OID_FIELD(serverid);
+	WRITE_OID_FIELD(userid);
+	WRITE_STRING_FIELD(explainInfo);
+	WRITE_FLOAT_FIELD(startup_cost, "%.2f");
+	WRITE_FLOAT_FIELD(total_cost, "%.2f");
+	WRITE_NODE_FIELD(fdw_private);
+}
+
+static void
 _outJoin(StringInfo str, Join *node)
 {
 	WRITE_NODE_TYPE("JOIN");
@@ -1476,6 +1497,14 @@ _outTidPath(StringInfo str, TidPath *node)
 }
 
 static void
+_outForeignPath(StringInfo str, ForeignPath *node)
+{
+	WRITE_NODE_TYPE("FOREIGNPATH");
+
+	_outPathInfo(str, (Path *) node);
+}
+
+static void
 _outAppendPath(StringInfo str, AppendPath *node)
 {
 	WRITE_NODE_TYPE("APPENDPATH");
@@ -2618,6 +2647,12 @@ _outNode(StringInfo str, void *obj)
 			case T_WorkTableScan:
 				_outWorkTableScan(str, obj);
 				break;
+			case T_ForeignScan:
+				_outForeignScan(str, obj);
+				break;
+			case T_FdwPlan:
+				_outFdwPlan(str, obj);
+				break;
 			case T_Join:
 				_outJoin(str, obj);
 				break;
@@ -2820,6 +2855,9 @@ _outNode(StringInfo str, void *obj)
 			case T_TidPath:
 				_outTidPath(str, obj);
 				break;
+			case T_ForeignPath:
+				_outForeignPath(str, obj);
+				break;
 			case T_AppendPath:
 				_outAppendPath(str, obj);
 				break;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 37d8332..9a7bc4b 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -17,6 +17,7 @@
 
 #include <math.h>
 
+#include "catalog/pg_class.h"
 #include "nodes/nodeFuncs.h"
 #ifdef OPTIMIZER_DEBUG
 #include "nodes/print.h"
@@ -34,6 +35,7 @@
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
+#include "utils/lsyscache.h"
 
 
 /* These parameters are set by GUC */
@@ -255,14 +257,22 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	 * least one dimension of cost or sortedness.
 	 */
 
-	/* Consider sequential scan */
-	add_path(rel, create_seqscan_path(root, rel));
+	if (get_rel_relkind(rte->relid) == RELKIND_FOREIGN_TABLE)
+	{
+		/* only foreign scan path is applyable to foreign table */
+		add_path(rel, create_foreignscan_path(root, rel));
+	}
+	else
+	{
+		/* Consider sequential scan */
+		add_path(rel, create_seqscan_path(root, rel));
 
-	/* Consider index scans */
-	create_index_paths(root, rel);
+		/* Consider index scans */
+		create_index_paths(root, rel);
 
-	/* Consider TID scans */
-	create_tidscan_paths(root, rel);
+		/* Consider TID scans */
+		create_tidscan_paths(root, rel);
+	}
 
 	/* Now find the cheapest of the paths for this rel */
 	set_cheapest(rel);
@@ -1503,6 +1513,9 @@ print_path(PlannerInfo *root, Path *path, int indent)
 		case T_TidPath:
 			ptype = "TidScan";
 			break;
+		case T_ForeignPath:
+			ptype = "ForeignScan";
+			break;
 		case T_AppendPath:
 			ptype = "Append";
 			break;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index c74125f..1ed7255 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -20,6 +20,7 @@
 #include <math.h>
 
 #include "access/skey.h"
+#include "catalog/pg_class.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -71,6 +72,8 @@ static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
 					List *tlist, List *scan_clauses);
 static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
 						  List *tlist, List *scan_clauses);
+static ForeignScan *create_foreignscan_plan(PlannerInfo *root, Path *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,
@@ -112,6 +115,8 @@ static CteScan *make_ctescan(List *qptlist, List *qpqual,
 			 Index scanrelid, int ctePlanId, int cteParam);
 static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
 				   Index scanrelid, int wtParam);
+static ForeignScan *make_foreignscan(List *qptlist, RangeTblEntry *rte,
+				 List *qpqual, Index scanrelid, FdwPlan * fplan);
 static BitmapAnd *make_bitmap_and(List *bitmapplans);
 static BitmapOr *make_bitmap_or(List *bitmapplans);
 static NestLoop *make_nestloop(List *tlist,
@@ -204,6 +209,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 		case T_ValuesScan:
 		case T_CteScan:
 		case T_WorkTableScan:
+		case T_ForeignScan:
 			plan = create_scan_plan(root, best_path);
 			break;
 		case T_HashJoin:
@@ -344,6 +350,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
 													  scan_clauses);
 			break;
 
+		case T_ForeignScan:
+			plan = (Plan *) create_foreignscan_plan(root,
+													best_path,
+													tlist,
+													scan_clauses);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) best_path->pathtype);
@@ -466,6 +479,7 @@ disuse_physical_tlist(Plan *plan, Path *path)
 		case T_ValuesScan:
 		case T_CteScan:
 		case T_WorkTableScan:
+		case T_ForeignScan:
 			plan->targetlist = build_relation_tlist(path->parent);
 			break;
 		default:
@@ -1747,6 +1761,43 @@ create_worktablescan_plan(PlannerInfo *root, Path *best_path,
 	return scan_plan;
 }
 
+/*
+ * create_foreignscan_plan
+ *	 Returns a foreignscan plan for the base relation scanned by 'best_path'
+ *	 with restriction clauses 'scan_clauses' and targetlist 'tlist'.
+ */
+static ForeignScan *
+create_foreignscan_plan(PlannerInfo *root, Path *best_path,
+						List *tlist, List *scan_clauses)
+{
+	ForeignPath *fpath = (ForeignPath *) best_path;
+	ForeignScan *scan_plan;
+	Index		scan_relid = best_path->parent->relid;
+	RangeTblEntry *rte;
+
+	/* it should be a base rel... */
+	Assert(scan_relid > 0);
+	Assert(best_path->parent->rtekind == RTE_RELATION);
+	rte = planner_rt_fetch(scan_relid, root);
+	Assert(rte->rtekind == RTE_RELATION);
+
+	/* 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);
+
+	scan_plan = make_foreignscan(tlist,
+								 rte,
+								 scan_clauses,
+								 scan_relid,
+								 fpath->fplan);
+
+	copy_path_costsize(&scan_plan->scan.plan, best_path);
+
+	return scan_plan;
+}
+
 
 /*****************************************************************************
  *
@@ -2253,10 +2304,10 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
 		return NULL;
 	if (IsA(node, Var))
 	{
-		Var	   *var = (Var *) node;
-		Param  *param;
+		Var		   *var = (Var *) node;
+		Param	   *param;
 		NestLoopParam *nlp;
-		ListCell *lc;
+		ListCell   *lc;
 
 		/* Upper-level Vars should be long gone at this point */
 		Assert(var->varlevelsup == 0);
@@ -2962,6 +3013,27 @@ make_worktablescan(List *qptlist,
 	return node;
 }
 
+static ForeignScan *
+make_foreignscan(List *qptlist,
+				 RangeTblEntry *rte,
+				 List *qpqual,
+				 Index scanrelid,
+				 FdwPlan *fplan)
+{
+	ForeignScan *node = makeNode(ForeignScan);
+	Plan	   *plan = &node->scan.plan;
+
+	/* cost should be inserted by caller */
+	plan->targetlist = qptlist;
+	plan->qual = qpqual;
+	plan->lefttree = NULL;
+	plan->righttree = NULL;
+	node->scan.scanrelid = scanrelid;
+	node->fplan = fplan;
+
+	return node;
+}
+
 Append *
 make_append(List *appendplans, List *tlist)
 {
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b52eebe..2004057 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1911,7 +1911,7 @@ preprocess_rowmarks(PlannerInfo *root)
 		newrc = makeNode(PlanRowMark);
 		newrc->rti = newrc->prti = i;
 		/* real tables support REFERENCE, anything else needs COPY */
-		if (rte->rtekind == RTE_RELATION)
+		if (rte->rtekind == RTE_RELATION && get_rel_relkind(rte->relid) != RELKIND_FOREIGN_TABLE)
 			newrc->markType = ROW_MARK_REFERENCE;
 		else
 			newrc->markType = ROW_MARK_COPY;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 02f5cab..ef2833a 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -400,6 +400,17 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
 					fix_scan_list(glob, splan->scan.plan.qual, rtoffset);
 			}
 			break;
+		case T_ForeignScan:
+			{
+				ForeignScan    *splan = (ForeignScan *) plan;
+
+				splan->scan.scanrelid += rtoffset;
+				splan->scan.plan.targetlist =
+					fix_scan_list(glob, splan->scan.plan.targetlist, rtoffset);
+				splan->scan.plan.qual =
+					fix_scan_list(glob, splan->scan.plan.qual, rtoffset);
+			}
+			break;
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index febec1e..54846da 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2044,6 +2044,10 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_ForeignScan:
+			context.paramids = bms_add_members(context.paramids, scan_params);
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index d1010af..658e856 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -17,6 +17,7 @@
 #include <math.h>
 
 #include "catalog/pg_operator.h"
+#include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -1420,6 +1421,45 @@ create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel)
 }
 
 /*
+ * create_foreignscan_path
+ *	  Creates a path corresponding to a scan of a foreign table,
+ *	  returning the pathnode.
+ */
+Path *
+create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel)
+{
+	RangeTblEntry  *rte;
+	FdwRoutine	   *routine;
+	ForeignPath	   *pathnode = makeNode(ForeignPath);
+	ForeignTable   *ftable;
+
+	pathnode->path.pathtype = T_ForeignScan;
+	pathnode->path.parent = rel;
+	pathnode->path.pathkeys = NIL;	/* result is always unordered */
+
+	rte = planner_rt_fetch(rel->relid, root);
+	routine = GetFdwRoutineByRelId(rte->relid);
+	if (routine->PlanRelScan == NULL)
+		ereport(ERROR,
+				(errmsg("foreign-data wrapper must support PlanRelScan to scan foreign table")));
+
+	ftable = GetForeignTable(rte->relid);
+
+	/* Let the FDW do its planning */
+	pathnode->fplan = routine->PlanRelScan(rte->relid, root, rel);
+	if (pathnode->fplan == NULL)
+		elog(ERROR, "PlanRelScan returned NULL");
+	pathnode->fplan->serverid = ftable->serverid;
+	pathnode->fplan->userid = GetUserId();
+
+	/* use costs estimated by FDW */
+	pathnode->path.startup_cost = pathnode->fplan->startup_cost;
+	pathnode->path.total_cost = pathnode->fplan->total_cost;
+
+	return (Path *) pathnode;
+}
+
+/*
  * create_nestloop_path
  *	  Creates a pathnode corresponding to a nestloop join between two
  *	  relations.
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index adbe45c..1d60af8 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -90,12 +90,6 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	 */
 	relation = heap_open(relationObjectId, NoLock);
 
-	/* Foreign table scans are not implemented yet. */
-	if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("foreign table scans are not yet supported")));
-
 	rel->min_attr = FirstLowInvalidHeapAttributeNumber + 1;
 	rel->max_attr = RelationGetNumberOfAttributes(relation);
 	rel->reltablespace = RelationGetForm(relation)->reltablespace;
@@ -460,6 +454,11 @@ estimate_rel_size(Relation rel, int32 *attr_widths,
 			*pages = 1;
 			*tuples = 1;
 			break;
+		case RELKIND_FOREIGN_TABLE:
+			/* foreign tables has no storage, trust statistics  */
+			*pages = rel->rd_rel->relpages;
+			*tuples = rel->rd_rel->reltuples;
+			break;
 		default:
 			/* else it has no disk storage; probably shouldn't get here? */
 			*pages = 0;
@@ -759,7 +758,8 @@ relation_excluded_by_constraints(PlannerInfo *root,
  *
  * We also support building a "physical" tlist for subqueries, functions,
  * values lists, and CTEs, since the same optimization can occur in
- * SubqueryScan, FunctionScan, ValuesScan, CteScan, and WorkTableScan nodes.
+ * SubqueryScan, FunctionScan, ValuesScan, CteScan, WorkTableScan and
+ * ForeignScan nodes.
  */
 List *
 build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 070e4c1..32bd5f7 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -40,6 +40,7 @@
 #include "parser/parse_target.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
 
 
@@ -2163,9 +2164,13 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					applyLockingClause(qry, i,
-									   lc->forUpdate, lc->noWait, pushedDown);
-					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					/* ignore foreign tables */
+					if (get_rel_relkind(rte->relid) != RELKIND_FOREIGN_TABLE)
+					{
+						applyLockingClause(qry, i,
+										   lc->forUpdate, lc->noWait, pushedDown);
+						rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+					}
 					break;
 				case RTE_SUBQUERY:
 					applyLockingClause(qry, i,
@@ -2212,6 +2217,11 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
+							if (get_rel_relkind(rte->relid) == RELKIND_FOREIGN_TABLE)
+								ereport(ERROR,
+										(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+										 errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a foreign table"),
+										 parser_errposition(pstate, thisrel->location)));
 							applyLockingClause(qry, i,
 											   lc->forUpdate, lc->noWait,
 											   pushedDown);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2178282..9e0c148 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -306,6 +306,9 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
 				create_generic_options alter_generic_options
 				relation_expr_list dostmt_opt_list
 
+%type <list>	opt_func_options func_options
+%type <defelt>	fdw_option
+
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target
 
@@ -3196,16 +3199,33 @@ DropTableSpaceStmt: DROP TABLESPACE name
  *
  *****************************************************************************/
 
-CreateFdwStmt: CREATE FOREIGN DATA_P WRAPPER name opt_validator create_generic_options
+CreateFdwStmt: CREATE FOREIGN DATA_P WRAPPER name opt_func_options create_generic_options
 				{
 					CreateFdwStmt *n = makeNode(CreateFdwStmt);
 					n->fdwname = $5;
-					n->validator = $6;
+					n->func_options = $6;
 					n->options = $7;
 					$$ = (Node *) n;
 				}
 		;
 
+fdw_option:
+			VALIDATOR handler_name				{ $$ = makeDefElem("validator", (Node *)$2); }
+			| NO VALIDATOR						{ $$ = makeDefElem("validator", NULL); }
+			| HANDLER handler_name				{ $$ = makeDefElem("handler", (Node *)$2); }
+			| NO HANDLER						{ $$ = makeDefElem("handler", NULL); }
+		;
+
+func_options:
+			fdw_option							{ $$ = list_make1($1); }
+			| func_options fdw_option			{ $$ = lappend($1, $2); }
+		;
+
+opt_func_options:
+			func_options						{ $$ = $1; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 /*****************************************************************************
  *
  * 		QUERY :
@@ -3238,28 +3258,20 @@ DropFdwStmt: DROP FOREIGN DATA_P WRAPPER name opt_drop_behavior
  *
  ****************************************************************************/
 
-AlterFdwStmt: ALTER FOREIGN DATA_P WRAPPER name validator_clause alter_generic_options
+AlterFdwStmt: ALTER FOREIGN DATA_P WRAPPER name opt_func_options alter_generic_options
 				{
 					AlterFdwStmt *n = makeNode(AlterFdwStmt);
 					n->fdwname = $5;
-					n->validator = $6;
-					n->change_validator = true;
+					n->func_options = $6;
 					n->options = $7;
 					$$ = (Node *) n;
 				}
-			| ALTER FOREIGN DATA_P WRAPPER name validator_clause
+			| ALTER FOREIGN DATA_P WRAPPER name func_options
 				{
 					AlterFdwStmt *n = makeNode(AlterFdwStmt);
 					n->fdwname = $5;
-					n->validator = $6;
-					n->change_validator = true;
-					$$ = (Node *) n;
-				}
-			| ALTER FOREIGN DATA_P WRAPPER name alter_generic_options
-				{
-					AlterFdwStmt *n = makeNode(AlterFdwStmt);
-					n->fdwname = $5;
-					n->options = $6;
+					n->func_options = $6;
+					n->options = NIL;
 					$$ = (Node *) n;
 				}
 		;
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index c12d4cb..c72db50 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1390,8 +1390,12 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
-			applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
-			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+			/* ignore foreign tables */
+			if (get_rel_relkind(rte->relid) != RELKIND_FOREIGN_TABLE)
+			{
+				applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
+				rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+			}
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 2be0696..779844d 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -453,3 +453,29 @@ pg_node_tree_send(PG_FUNCTION_ARGS)
 {
 	return textsend(fcinfo);
 }
+
+/*
+ * fdw_handler_in		- input routine for pseudo-type FDW_HANDLER.
+ */
+Datum
+fdw_handler_in(PG_FUNCTION_ARGS)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("cannot accept a value of type fdw_handler")));
+
+	PG_RETURN_VOID();			/* keep compiler quiet */
+}
+
+/*
+ * fdw_handler_out		- output routine for pseudo-type FDW_HANDLER.
+ */
+Datum
+fdw_handler_out(PG_FUNCTION_ARGS)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("cannot display a value of type fdw_handler")));
+
+	PG_RETURN_VOID();			/* keep compiler quiet */
+}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d3eb766..8baf79f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5985,6 +5985,7 @@ getForeignDataWrappers(int *numForeignDataWrappers)
 	int			i_fdwname;
 	int			i_rolname;
 	int			i_fdwvalidator;
+	int			i_fdwhandler;
 	int			i_fdwacl;
 	int			i_fdwoptions;
 
@@ -5998,13 +5999,27 @@ getForeignDataWrappers(int *numForeignDataWrappers)
 	/* Make sure we are in proper schema */
 	selectSourceSchema("pg_catalog");
 
-	appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, "
-		"(%s fdwowner) AS rolname, fdwvalidator::pg_catalog.regproc, fdwacl,"
-					  "array_to_string(ARRAY("
-		 "		SELECT option_name || ' ' || quote_literal(option_value) "
-	   "		FROM pg_options_to_table(fdwoptions)), ', ') AS fdwoptions "
-					  "FROM pg_foreign_data_wrapper",
-					  username_subquery);
+	if (g_fout->remoteVersion >= 90100)
+	{
+		appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, "
+			"(%s fdwowner) AS rolname, fdwvalidator::pg_catalog.regproc, "
+			"fdwhandler::pg_catalog.regproc, fdwacl,"
+						  "array_to_string(ARRAY("
+			 "		SELECT option_name || ' ' || quote_literal(option_value) "
+		   "		FROM pg_options_to_table(fdwoptions)), ', ') AS fdwoptions "
+						  "FROM pg_foreign_data_wrapper",
+						  username_subquery);
+	}
+	else
+	{
+		appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, "
+			"(%s fdwowner) AS rolname, fdwvalidator::pg_catalog.regproc, fdwacl,"
+						  "array_to_string(ARRAY("
+			 "		SELECT option_name || ' ' || quote_literal(option_value) "
+		   "		FROM pg_options_to_table(fdwoptions)), ', ') AS fdwoptions "
+						  "FROM pg_foreign_data_wrapper",
+						  username_subquery);
+	}
 
 	res = PQexec(g_conn, query->data);
 	check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK);
@@ -6019,6 +6034,8 @@ getForeignDataWrappers(int *numForeignDataWrappers)
 	i_fdwname = PQfnumber(res, "fdwname");
 	i_rolname = PQfnumber(res, "rolname");
 	i_fdwvalidator = PQfnumber(res, "fdwvalidator");
+	if (g_fout->remoteVersion >= 90100)
+		i_fdwhandler = PQfnumber(res, "fdwhandler");
 	i_fdwacl = PQfnumber(res, "fdwacl");
 	i_fdwoptions = PQfnumber(res, "fdwoptions");
 
@@ -6032,6 +6049,10 @@ getForeignDataWrappers(int *numForeignDataWrappers)
 		fdwinfo[i].dobj.namespace = NULL;
 		fdwinfo[i].rolname = strdup(PQgetvalue(res, i, i_rolname));
 		fdwinfo[i].fdwvalidator = strdup(PQgetvalue(res, i, i_fdwvalidator));
+		if (g_fout->remoteVersion >= 90100)
+			fdwinfo[i].fdwhandler = strdup(PQgetvalue(res, i, i_fdwhandler));
+		else
+			fdwinfo[i].fdwhandler = NULL;
 		fdwinfo[i].fdwoptions = strdup(PQgetvalue(res, i, i_fdwoptions));
 		fdwinfo[i].fdwacl = strdup(PQgetvalue(res, i, i_fdwacl));
 
@@ -10337,6 +10358,11 @@ dumpForeignDataWrapper(Archive *fout, FdwInfo *fdwinfo)
 		appendPQExpBuffer(q, " VALIDATOR %s",
 						  fdwinfo->fdwvalidator);
 
+	if (g_fout->remoteVersion >= 90100 && fdwinfo->fdwhandler &&
+		strcmp(fdwinfo->fdwhandler, "-") != 0)
+		appendPQExpBuffer(q, " HANDLER %s",
+						  fdwinfo->fdwhandler);
+
 	if (fdwinfo->fdwoptions && strlen(fdwinfo->fdwoptions) > 0)
 		appendPQExpBuffer(q, " OPTIONS (%s)", fdwinfo->fdwoptions);
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 43fd1ad..c630a1d 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -421,6 +421,7 @@ typedef struct _fdwInfo
 	DumpableObject dobj;
 	char	   *rolname;
 	char	   *fdwvalidator;
+	char	   *fdwhandler;
 	char	   *fdwoptions;
 	char	   *fdwacl;
 } FdwInfo;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ada04de..57718eb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3480,9 +3480,11 @@ listForeignDataWrappers(const char *pattern, bool verbose)
 	printfPQExpBuffer(&buf,
 					  "SELECT fdwname AS \"%s\",\n"
 					  "  pg_catalog.pg_get_userbyid(fdwowner) AS \"%s\",\n"
+					  "  fdwhandler::pg_catalog.regproc AS \"%s\",\n"
 					  "  fdwvalidator::pg_catalog.regproc AS \"%s\"",
 					  gettext_noop("Name"),
 					  gettext_noop("Owner"),
+					  gettext_noop("Handler"),
 					  gettext_noop("Validator"));
 
 	if (verbose)
diff --git a/src/include/catalog/pg_foreign_data_wrapper.h b/src/include/catalog/pg_foreign_data_wrapper.h
index a83556d..971326c 100644
--- a/src/include/catalog/pg_foreign_data_wrapper.h
+++ b/src/include/catalog/pg_foreign_data_wrapper.h
@@ -33,6 +33,7 @@ CATALOG(pg_foreign_data_wrapper,2328)
 	NameData	fdwname;		/* foreign-data wrapper name */
 	Oid			fdwowner;		/* FDW owner */
 	Oid			fdwvalidator;	/* optional validation function */
+	Oid			fdwhandler;		/* foreign-data routines function */
 
 	/* VARIABLE LENGTH FIELDS start here. */
 
@@ -52,11 +53,12 @@ typedef FormData_pg_foreign_data_wrapper *Form_pg_foreign_data_wrapper;
  * ----------------
  */
 
-#define Natts_pg_foreign_data_wrapper				5
+#define Natts_pg_foreign_data_wrapper				6
 #define Anum_pg_foreign_data_wrapper_fdwname		1
 #define Anum_pg_foreign_data_wrapper_fdwowner		2
 #define Anum_pg_foreign_data_wrapper_fdwvalidator	3
-#define Anum_pg_foreign_data_wrapper_fdwacl			4
-#define Anum_pg_foreign_data_wrapper_fdwoptions		5
+#define Anum_pg_foreign_data_wrapper_fdwhandler		4
+#define Anum_pg_foreign_data_wrapper_fdwacl			5
+#define Anum_pg_foreign_data_wrapper_fdwoptions		6
 
 #endif   /* PG_FOREIGN_DATA_WRAPPER_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f8b5d4d..05fc57c 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3899,6 +3899,10 @@ DATA(insert OID = 2777 (  anynonarray_in	PGNSP PGUID 12 1 0 0 f f f t f i 1 0 27
 DESCR("I/O");
 DATA(insert OID = 2778 (  anynonarray_out	PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2275 "2776" _null_ _null_ _null_ _null_ anynonarray_out _null_ _null_ _null_ ));
 DESCR("I/O");
+DATA(insert OID = 3116 (  fdw_handler_in	PGNSP PGUID 12 1 0 0 f f f f f i 1 0 3115 "2275" _null_ _null_ _null_ _null_ fdw_handler_in _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 3117 (  fdw_handler_out	PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2275 "3115" _null_ _null_ _null_ _null_ fdw_handler_out _null_ _null_ _null_ ));
+DESCR("I/O");
 
 /* cryptographic */
 DATA(insert OID =  2311 (  md5	   PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "25" _null_ _null_ _null_ _null_ md5_text _null_ _null_ _null_ ));
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 620f7b4..ee40705 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -623,6 +623,8 @@ DATA(insert OID = 2776 ( anynonarray	PGNSP PGUID  4 t p P f t \054 0 0 0 anynona
 #define ANYNONARRAYOID	2776
 DATA(insert OID = 3500 ( anyenum		PGNSP PGUID  4 t p P f t \054 0 0 0 anyenum_in anyenum_out - - - - - i p f 0 -1 0 _null_ _null_ ));
 #define ANYENUMOID		3500
+DATA(insert OID = 3115 ( fdw_handler	PGNSP PGUID  4 t p P f t \054 0 0 0 fdw_handler_in fdw_handler_out - - - - - i p f 0 -1 0 _null_ _null_ ));
+#define FDW_HANDLEROID		3115
 
 
 /*
diff --git a/src/include/executor/nodeForeignscan.h b/src/include/executor/nodeForeignscan.h
new file mode 100644
index 0000000..2aaaf22
--- /dev/null
+++ b/src/include/executor/nodeForeignscan.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeForeignscan.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/nodeForeignscan.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODEFOREIGNSCAN_H
+#define NODEFOREIGNSCAN_H
+
+#include "nodes/execnodes.h"
+
+extern ForeignScanState *ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecForeignScan(ForeignScanState *node);
+extern void ExecEndForeignScan(ForeignScanState *node);
+extern void ExecForeignMarkPos(ForeignScanState *node);
+extern void ExecForeignRestrPos(ForeignScanState *node);
+extern void ExecForeignReScan(ForeignScanState *node);
+
+#endif   /* NODEFOREIGNSCAN_H */
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
new file mode 100644
index 0000000..22e7a8e
--- /dev/null
+++ b/src/include/foreign/fdwapi.h
@@ -0,0 +1,159 @@
+/*-------------------------------------------------------------------------
+ *
+ * fdwapi.h
+ *	  API for foreign-data wrappers
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ *
+ * src/include/foreign/fdwapi.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FDWAPI_H
+#define FDWAPI_H
+
+#include "executor/tuptable.h"
+#include "nodes/pg_list.h"
+#include "nodes/relation.h"
+
+/*
+ * When a plan is going to be cached, the plan node is copied into another
+ * context with copyObject. It means that FdwPlan, a part of ForeignScan plan
+ * node, and its contents must have copyObject support too.
+ */
+struct FdwPlan
+{
+	NodeTag		type;
+
+	Oid			serverid;
+	Oid			userid;
+
+	/*
+	 * Free-form text shown in EXPLAIN. The SQL to be sent to the remote
+	 * server is typically shown here.
+	 */
+	char	   *explainInfo;
+
+	/*
+	 * Cost estimation info. The startup_cost should include the cost of
+	 * connecting to the remote host and sending over the query, as well as
+	 * the cost of starting up the query so that it returns the first result
+	 * row.
+	 */
+	double		startup_cost;
+	double		total_cost;
+#ifdef NOT_USED
+	/* XXX: Should FDWs estimate rows and width? */
+	double		rows;
+	int			width;
+#endif
+
+	/*
+	 * FDW-private data. FDW must guarantee that every elements in this list
+	 * have copyObject support.  If FDW needs to store arbitrary data such as
+	 * non-Node structure, Const of bytea can be used as a container.
+	 */
+	List	   *fdw_private;
+};
+typedef struct FdwPlan FdwPlan;
+
+struct FdwExecutionState
+{
+	/* FDW-private data */
+	void	   *fdw_private;
+};
+typedef struct FdwExecutionState FdwExecutionState;
+
+/*
+ * Common interface routines of FDW, inspired by the FDW API in the SQL/MED
+ * standard, but adapted to the PostgreSQL world.
+ *
+ * A foreign-data wrapper implements these routines. At a minimum, it must
+ * implement PlanRelScan, BeginScan, Iterate, ReScan and EndScan.
+ *
+ * The PlanXXX functions return an FdwPlan struct that can later be executed
+ * with BeginScan. The implementation should fill in the cost estimates in
+ * FdwPlan, and may store private information.
+ */
+struct FdwRoutine
+{
+	/*
+	 * Plan a scan on a foreign table. 'foreigntableid' identifies the foreign
+	 * table.
+	 *
+	 * 'root' and 'baserel' contain context information that the
+	 * implementation can use to restrict the data that is fetched.
+	 * baserel->baserestrictinfo is particularly interesting, as it contains
+	 * quals (WHERE clauses) that can be used to filter the rows in the remote
+	 * server. 'root' and 'baserel' can be safely ignored, the planner will
+	 * re-check the quals on every fetched row anyway. baserel->reltargetlist
+	 * can be used to determine which columns need to be fetched.
+	 */
+	FdwPlan    *(*PlanRelScan) (Oid foreigntableid, PlannerInfo *root,
+											RelOptInfo *baserel);
+
+	/*
+	 * Begin execution of a foreign scan.
+	 *
+	 * This function is only called when an actual scan is needed, EXPLAIN
+	 * without ANALYZE option doesn't call BeginScan().
+	 */
+	FdwExecutionState *(*BeginScan) (FdwPlan * plan, ParamListInfo params);
+
+	/*
+	 * Fetch the next record and store it into slot.
+	 *
+	 * FDW can return result as either a physical tuple or a virtual tuple. If
+	 * FDW returns virtual tuple, executor will materialize the virtual tuple
+	 * and store tableoid in it.
+	 *
+	 * When the end of external data is reached, FDW should clear the slot
+	 * with ExecClearTuple(slot) to tell executor to finish the scan.
+	 *
+	 * Note that Iterate is called in per-tuple memory context which is reset
+	 * before each call.
+	 */
+	void		(*Iterate) (FdwExecutionState * state, TupleTableSlot *slot);
+
+	/*
+	 * Reset the read pointer to the head of the scan.
+	 *
+	 * This function will be called when a new outer tuple is acquired in a
+	 * nested loop. Optional, if not defined the executor will call EndScan
+	 * and BeginScan.
+	 */
+	void		(*ReScan) (FdwExecutionState * state);
+
+	/*
+	 * End the foreign scan and do clean up.
+	 */
+	void		(*EndScan) (FdwExecutionState * state);
+
+#ifdef NOT_USED
+
+	/*
+	 * Plan a query of arbitrary native SQL (or other query language supported
+	 * by the foreign server). This is used for SQL/MED passthrough mode, or
+	 * e.g contrib/dblink.
+	 */
+	FdwPlan    *(*PlanNative) (Oid serverid, char *query);
+
+	/*
+	 * Plan a whole subquery. This is used for example to execute an aggregate
+	 * query remotely without pulling all the rows to the local server.
+	 *
+	 * The implementation can return NULL if it cannot satisfy the whole
+	 * subquery, in which case the planner will break down the query into
+	 * smaller parts and call PlanRelScan for the foreign tables involved.
+	 *
+	 * The implementation must be careful to only accept queries it fully
+	 * understands! For example, if it ignores windowClauses, and returns a
+	 * non-NULL results for a query that contains one, the windowClause would
+	 * be lost and the query would return incorrect results.
+	 */
+	FdwPlan    *(*PlanQuery) (PlannerInfo *root, Query query);
+#endif
+};
+typedef struct FdwRoutine FdwRoutine;
+
+#endif   /* FDWAPI_H */
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 2d1495c..b0cb37c 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -13,6 +13,7 @@
 #ifndef FOREIGN_H
 #define FOREIGN_H
 
+#include "foreign/fdwapi.h"
 #include "nodes/parsenodes.h"
 
 
@@ -38,6 +39,7 @@ typedef struct ForeignDataWrapper
 	Oid			owner;			/* FDW owner user Oid */
 	char	   *fdwname;		/* Name of the FDW */
 	Oid			fdwvalidator;
+	Oid			fdwhandler;
 	List	   *options;		/* fdwoptions as DefElem list */
 } ForeignDataWrapper;
 
@@ -59,6 +61,12 @@ typedef struct UserMapping
 	List	   *options;		/* useoptions as DefElem list */
 } UserMapping;
 
+typedef struct ForeignTable
+{
+	Oid			relid;			/* relation Oid */
+	Oid			serverid;		/* server Oid */
+	List	   *options;		/* ftoptions as DefElem list */
+} ForeignTable;
 
 extern ForeignServer *GetForeignServer(Oid serverid);
 extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
@@ -68,5 +76,8 @@ extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
 extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
 							bool missing_ok);
 extern Oid	GetForeignDataWrapperOidByName(const char *name, bool missing_ok);
+extern ForeignTable *GetForeignTable(Oid relid);
+extern FdwRoutine *GetFdwRoutineByRelId(Oid relid);
+extern FdwRoutine *GetFdwRoutine(Oid fdwhandler);
 
 #endif   /* FOREIGN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 546b581..7d1c681 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -17,8 +17,10 @@
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/skey.h"
+#include "foreign/fdwapi.h"
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
+#include "nodes/relation.h"
 #include "nodes/tidbitmap.h"
 #include "utils/hsearch.h"
 #include "utils/rel.h"
@@ -1402,6 +1404,20 @@ typedef struct WorkTableScanState
 	RecursiveUnionState *rustate;
 } WorkTableScanState;
 
+/* ----------------
+ *	 ForeignScanState information
+ *
+ *		ForeignScan nodes are used to scan the foreign table managed by
+ *		a foreign server.
+ * ----------------
+ */
+typedef struct ForeignScanState
+{
+	ScanState		ss;			/* its first field is NodeTag */
+	FdwRoutine	   *routine;
+	FdwExecutionState *fstate;	/* private data for each data wrapper */
+} ForeignScanState;
+
 /* ----------------------------------------------------------------
  *				 Join State Information
  * ----------------------------------------------------------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 456e8e2..5b065b5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -60,6 +60,8 @@ typedef enum NodeTag
 	T_ValuesScan,
 	T_CteScan,
 	T_WorkTableScan,
+	T_ForeignScan,
+	T_FdwPlan,
 	T_Join,
 	T_NestLoop,
 	T_MergeJoin,
@@ -103,6 +105,7 @@ typedef enum NodeTag
 	T_ValuesScanState,
 	T_CteScanState,
 	T_WorkTableScanState,
+	T_ForeignScanState,
 	T_JoinState,
 	T_NestLoopState,
 	T_MergeJoinState,
@@ -216,6 +219,7 @@ typedef enum NodeTag
 	T_MergePath,
 	T_HashPath,
 	T_TidPath,
+	T_ForeignPath,
 	T_AppendPath,
 	T_MergeAppendPath,
 	T_ResultPath,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 483f225..2db3c48 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1531,7 +1531,7 @@ typedef struct CreateFdwStmt
 {
 	NodeTag		type;
 	char	   *fdwname;		/* foreign-data wrapper name */
-	List	   *validator;		/* optional validator function (qual. name) */
+	List	   *func_options;	/* VALIDATOR/HANDLER conbination */
 	List	   *options;		/* generic options to FDW */
 } CreateFdwStmt;
 
@@ -1539,8 +1539,7 @@ typedef struct AlterFdwStmt
 {
 	NodeTag		type;
 	char	   *fdwname;		/* foreign-data wrapper name */
-	List	   *validator;		/* optional validator function (qual. name) */
-	bool		change_validator;
+	List	   *func_options;	/* VALIDATOR/HANDLER conbination */
 	List	   *options;		/* generic options to FDW */
 } AlterFdwStmt;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 011c686..fcfd203 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -15,6 +15,7 @@
 #define PLANNODES_H
 
 #include "access/sdir.h"
+#include "foreign/fdwapi.h"
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
 #include "storage/itemptr.h"
@@ -434,6 +435,16 @@ typedef struct WorkTableScan
 	int			wtParam;		/* ID of Param representing work table */
 } WorkTableScan;
 
+/* ----------------
+ *		ForeignScan node
+ * ----------------
+ */
+typedef struct ForeignScan
+{
+	Scan			scan;
+	FdwPlan		   *fplan;
+} ForeignScan;
+
 
 /*
  * ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 8b14838..133a2bd 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -745,6 +745,15 @@ typedef struct TidPath
 } TidPath;
 
 /*
+ * ForeignPath represents a scan on a foreign table
+ */
+typedef struct ForeignPath
+{
+	Path		path;
+	struct FdwPlan *fplan;
+} ForeignPath;
+
+/*
  * AppendPath represents an Append plan, ie, successive execution of
  * several member plans.
  *
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index ff220e3..fe74600 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -61,6 +61,7 @@ extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel);
 extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel);
 extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel);
 extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel);
+extern Path *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel);
 
 extern NestPath *create_nestloop_path(PlannerInfo *root,
 					 RelOptInfo *joinrel,
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 4cb6c5c..eaaf7d0 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -518,6 +518,8 @@ extern Datum pg_node_tree_in(PG_FUNCTION_ARGS);
 extern Datum pg_node_tree_out(PG_FUNCTION_ARGS);
 extern Datum pg_node_tree_recv(PG_FUNCTION_ARGS);
 extern Datum pg_node_tree_send(PG_FUNCTION_ARGS);
+extern Datum fdw_handler_in(PG_FUNCTION_ARGS);
+extern Datum fdw_handler_out(PG_FUNCTION_ARGS);
 
 /* regexp.c */
 extern Datum nameregexeq(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index d6c650b..8f7eac5 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -16,11 +16,11 @@ CREATE ROLE unprivileged_role;
 CREATE FOREIGN DATA WRAPPER dummy;
 CREATE FOREIGN DATA WRAPPER postgresql VALIDATOR postgresql_fdw_validator;
 -- At this point we should have 2 built-in wrappers and no servers.
-SELECT fdwname, fdwvalidator::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3;
-  fdwname   |       fdwvalidator       | fdwoptions 
-------------+--------------------------+------------
- dummy      | -                        | 
- postgresql | postgresql_fdw_validator | 
+SELECT fdwname, fdwvalidator::regproc, fdwhandler::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3;
+  fdwname   |       fdwvalidator       | fdwhandler | fdwoptions 
+------------+--------------------------+------------+------------
+ dummy      | -                        | -          | 
+ postgresql | postgresql_fdw_validator | -          | 
 (2 rows)
 
 SELECT srvname, srvoptions FROM pg_foreign_server;
@@ -38,12 +38,12 @@ CREATE FOREIGN DATA WRAPPER foo VALIDATOR bar;            -- ERROR
 ERROR:  function bar(text[], oid) does not exist
 CREATE FOREIGN DATA WRAPPER foo;
 \dew
-               List of foreign-data wrappers
-    Name    |       Owner       |        Validator         
-------------+-------------------+--------------------------
- dummy      | foreign_data_user | -
- foo        | foreign_data_user | -
- postgresql | foreign_data_user | postgresql_fdw_validator
+                    List of foreign-data wrappers
+    Name    |       Owner       | Handler |        Validator         
+------------+-------------------+---------+--------------------------
+ dummy      | foreign_data_user | -       | -
+ foo        | foreign_data_user | -       | -
+ postgresql | foreign_data_user | -       | postgresql_fdw_validator
 (3 rows)
 
 CREATE FOREIGN DATA WRAPPER foo; -- duplicate
@@ -51,12 +51,12 @@ ERROR:  foreign-data wrapper "foo" already exists
 DROP FOREIGN DATA WRAPPER foo;
 CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1');
 \dew+
-                                List of foreign-data wrappers
-    Name    |       Owner       |        Validator         | Access privileges |   Options   
-------------+-------------------+--------------------------+-------------------+-------------
- dummy      | foreign_data_user | -                        |                   | 
- foo        | foreign_data_user | -                        |                   | {testing=1}
- postgresql | foreign_data_user | postgresql_fdw_validator |                   | 
+                                     List of foreign-data wrappers
+    Name    |       Owner       | Handler |        Validator         | Access privileges |   Options   
+------------+-------------------+---------+--------------------------+-------------------+-------------
+ dummy      | foreign_data_user | -       | -                        |                   | 
+ foo        | foreign_data_user | -       | -                        |                   | {testing=1}
+ postgresql | foreign_data_user | -       | postgresql_fdw_validator |                   | 
 (3 rows)
 
 DROP FOREIGN DATA WRAPPER foo;
@@ -64,12 +64,12 @@ CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1', testing '2');   -- ERROR
 ERROR:  option "testing" provided more than once
 CREATE FOREIGN DATA WRAPPER foo OPTIONS (testing '1', another '2');
 \dew+
-                                     List of foreign-data wrappers
-    Name    |       Owner       |        Validator         | Access privileges |        Options        
-------------+-------------------+--------------------------+-------------------+-----------------------
- dummy      | foreign_data_user | -                        |                   | 
- foo        | foreign_data_user | -                        |                   | {testing=1,another=2}
- postgresql | foreign_data_user | postgresql_fdw_validator |                   | 
+                                          List of foreign-data wrappers
+    Name    |       Owner       | Handler |        Validator         | Access privileges |        Options        
+------------+-------------------+---------+--------------------------+-------------------+-----------------------
+ dummy      | foreign_data_user | -       | -                        |                   | 
+ foo        | foreign_data_user | -       | -                        |                   | {testing=1,another=2}
+ postgresql | foreign_data_user | -       | postgresql_fdw_validator |                   | 
 (3 rows)
 
 DROP FOREIGN DATA WRAPPER foo;
@@ -80,12 +80,12 @@ HINT:  Must be superuser to create a foreign-data wrapper.
 RESET ROLE;
 CREATE FOREIGN DATA WRAPPER foo VALIDATOR postgresql_fdw_validator;
 \dew+
-                              List of foreign-data wrappers
-    Name    |       Owner       |        Validator         | Access privileges | Options 
-------------+-------------------+--------------------------+-------------------+---------
- dummy      | foreign_data_user | -                        |                   | 
- foo        | foreign_data_user | postgresql_fdw_validator |                   | 
- postgresql | foreign_data_user | postgresql_fdw_validator |                   | 
+                                   List of foreign-data wrappers
+    Name    |       Owner       | Handler |        Validator         | Access privileges | Options 
+------------+-------------------+---------+--------------------------+-------------------+---------
+ dummy      | foreign_data_user | -       | -                        |                   | 
+ foo        | foreign_data_user | -       | postgresql_fdw_validator |                   | 
+ postgresql | foreign_data_user | -       | postgresql_fdw_validator |                   | 
 (3 rows)
 
 -- ALTER FOREIGN DATA WRAPPER
@@ -97,12 +97,12 @@ ALTER FOREIGN DATA WRAPPER foo VALIDATOR bar;               -- ERROR
 ERROR:  function bar(text[], oid) does not exist
 ALTER FOREIGN DATA WRAPPER foo NO VALIDATOR;
 \dew+
-                              List of foreign-data wrappers
-    Name    |       Owner       |        Validator         | Access privileges | Options 
-------------+-------------------+--------------------------+-------------------+---------
- dummy      | foreign_data_user | -                        |                   | 
- foo        | foreign_data_user | -                        |                   | 
- postgresql | foreign_data_user | postgresql_fdw_validator |                   | 
+                                   List of foreign-data wrappers
+    Name    |       Owner       | Handler |        Validator         | Access privileges | Options 
+------------+-------------------+---------+--------------------------+-------------------+---------
+ dummy      | foreign_data_user | -       | -                        |                   | 
+ foo        | foreign_data_user | -       | -                        |                   | 
+ postgresql | foreign_data_user | -       | postgresql_fdw_validator |                   | 
 (3 rows)
 
 ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '1', b '2');
@@ -112,34 +112,34 @@ ALTER FOREIGN DATA WRAPPER foo OPTIONS (DROP c);            -- ERROR
 ERROR:  option "c" not found
 ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD x '1', DROP x);
 \dew+
-                               List of foreign-data wrappers
-    Name    |       Owner       |        Validator         | Access privileges |  Options  
-------------+-------------------+--------------------------+-------------------+-----------
- dummy      | foreign_data_user | -                        |                   | 
- foo        | foreign_data_user | -                        |                   | {a=1,b=2}
- postgresql | foreign_data_user | postgresql_fdw_validator |                   | 
+                                    List of foreign-data wrappers
+    Name    |       Owner       | Handler |        Validator         | Access privileges |  Options  
+------------+-------------------+---------+--------------------------+-------------------+-----------
+ dummy      | foreign_data_user | -       | -                        |                   | 
+ foo        | foreign_data_user | -       | -                        |                   | {a=1,b=2}
+ postgresql | foreign_data_user | -       | postgresql_fdw_validator |                   | 
 (3 rows)
 
 ALTER FOREIGN DATA WRAPPER foo OPTIONS (DROP a, SET b '3', ADD c '4');
 \dew+
-                               List of foreign-data wrappers
-    Name    |       Owner       |        Validator         | Access privileges |  Options  
-------------+-------------------+--------------------------+-------------------+-----------
- dummy      | foreign_data_user | -                        |                   | 
- foo        | foreign_data_user | -                        |                   | {b=3,c=4}
- postgresql | foreign_data_user | postgresql_fdw_validator |                   | 
+                                    List of foreign-data wrappers
+    Name    |       Owner       | Handler |        Validator         | Access privileges |  Options  
+------------+-------------------+---------+--------------------------+-------------------+-----------
+ dummy      | foreign_data_user | -       | -                        |                   | 
+ foo        | foreign_data_user | -       | -                        |                   | {b=3,c=4}
+ postgresql | foreign_data_user | -       | postgresql_fdw_validator |                   | 
 (3 rows)
 
 ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '2');
 ALTER FOREIGN DATA WRAPPER foo OPTIONS (b '4');             -- ERROR
 ERROR:  option "b" provided more than once
 \dew+
-                                 List of foreign-data wrappers
-    Name    |       Owner       |        Validator         | Access privileges |    Options    
-------------+-------------------+--------------------------+-------------------+---------------
- dummy      | foreign_data_user | -                        |                   | 
- foo        | foreign_data_user | -                        |                   | {b=3,c=4,a=2}
- postgresql | foreign_data_user | postgresql_fdw_validator |                   | 
+                                      List of foreign-data wrappers
+    Name    |       Owner       | Handler |        Validator         | Access privileges |    Options    
+------------+-------------------+---------+--------------------------+-------------------+---------------
+ dummy      | foreign_data_user | -       | -                        |                   | 
+ foo        | foreign_data_user | -       | -                        |                   | {b=3,c=4,a=2}
+ postgresql | foreign_data_user | -       | postgresql_fdw_validator |                   | 
 (3 rows)
 
 SET ROLE regress_test_role;
@@ -149,12 +149,12 @@ HINT:  Must be superuser to alter a foreign-data wrapper.
 SET ROLE regress_test_role_super;
 ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD d '5');
 \dew+
-                                   List of foreign-data wrappers
-    Name    |       Owner       |        Validator         | Access privileges |      Options      
-------------+-------------------+--------------------------+-------------------+-------------------
- dummy      | foreign_data_user | -                        |                   | 
- foo        | foreign_data_user | -                        |                   | {b=3,c=4,a=2,d=5}
- postgresql | foreign_data_user | postgresql_fdw_validator |                   | 
+                                        List of foreign-data wrappers
+    Name    |       Owner       | Handler |        Validator         | Access privileges |      Options      
+------------+-------------------+---------+--------------------------+-------------------+-------------------
+ dummy      | foreign_data_user | -       | -                        |                   | 
+ foo        | foreign_data_user | -       | -                        |                   | {b=3,c=4,a=2,d=5}
+ postgresql | foreign_data_user | -       | postgresql_fdw_validator |                   | 
 (3 rows)
 
 ALTER FOREIGN DATA WRAPPER foo OWNER TO regress_test_role;  -- ERROR
@@ -168,12 +168,12 @@ ERROR:  permission denied to alter foreign-data wrapper "foo"
 HINT:  Must be superuser to alter a foreign-data wrapper.
 RESET ROLE;
 \dew+
-                                      List of foreign-data wrappers
-    Name    |          Owner          |        Validator         | Access privileges |      Options      
-------------+-------------------------+--------------------------+-------------------+-------------------
- dummy      | foreign_data_user       | -                        |                   | 
- foo        | regress_test_role_super | -                        |                   | {b=3,c=4,a=2,d=5}
- postgresql | foreign_data_user       | postgresql_fdw_validator |                   | 
+                                           List of foreign-data wrappers
+    Name    |          Owner          | Handler |        Validator         | Access privileges |      Options      
+------------+-------------------------+---------+--------------------------+-------------------+-------------------
+ dummy      | foreign_data_user       | -       | -                        |                   | 
+ foo        | regress_test_role_super | -       | -                        |                   | {b=3,c=4,a=2,d=5}
+ postgresql | foreign_data_user       | -       | postgresql_fdw_validator |                   | 
 (3 rows)
 
 -- DROP FOREIGN DATA WRAPPER
@@ -182,12 +182,12 @@ ERROR:  foreign-data wrapper "nonexistent" does not exist
 DROP FOREIGN DATA WRAPPER IF EXISTS nonexistent;
 NOTICE:  foreign-data wrapper "nonexistent" does not exist, skipping
 \dew+
-                                      List of foreign-data wrappers
-    Name    |          Owner          |        Validator         | Access privileges |      Options      
-------------+-------------------------+--------------------------+-------------------+-------------------
- dummy      | foreign_data_user       | -                        |                   | 
- foo        | regress_test_role_super | -                        |                   | {b=3,c=4,a=2,d=5}
- postgresql | foreign_data_user       | postgresql_fdw_validator |                   | 
+                                           List of foreign-data wrappers
+    Name    |          Owner          | Handler |        Validator         | Access privileges |      Options      
+------------+-------------------------+---------+--------------------------+-------------------+-------------------
+ dummy      | foreign_data_user       | -       | -                        |                   | 
+ foo        | regress_test_role_super | -       | -                        |                   | {b=3,c=4,a=2,d=5}
+ postgresql | foreign_data_user       | -       | postgresql_fdw_validator |                   | 
 (3 rows)
 
 DROP ROLE regress_test_role_super;                          -- ERROR
@@ -202,23 +202,23 @@ ALTER ROLE regress_test_role_super SUPERUSER;
 DROP FOREIGN DATA WRAPPER foo;
 DROP ROLE regress_test_role_super;
 \dew+
-                              List of foreign-data wrappers
-    Name    |       Owner       |        Validator         | Access privileges | Options 
-------------+-------------------+--------------------------+-------------------+---------
- dummy      | foreign_data_user | -                        |                   | 
- postgresql | foreign_data_user | postgresql_fdw_validator |                   | 
+                                   List of foreign-data wrappers
+    Name    |       Owner       | Handler |        Validator         | Access privileges | Options 
+------------+-------------------+---------+--------------------------+-------------------+---------
+ dummy      | foreign_data_user | -       | -                        |                   | 
+ postgresql | foreign_data_user | -       | postgresql_fdw_validator |                   | 
 (2 rows)
 
 CREATE FOREIGN DATA WRAPPER foo;
 CREATE SERVER s1 FOREIGN DATA WRAPPER foo;
 CREATE USER MAPPING FOR current_user SERVER s1;
 \dew+
-                              List of foreign-data wrappers
-    Name    |       Owner       |        Validator         | Access privileges | Options 
-------------+-------------------+--------------------------+-------------------+---------
- dummy      | foreign_data_user | -                        |                   | 
- foo        | foreign_data_user | -                        |                   | 
- postgresql | foreign_data_user | postgresql_fdw_validator |                   | 
+                                   List of foreign-data wrappers
+    Name    |       Owner       | Handler |        Validator         | Access privileges | Options 
+------------+-------------------+---------+--------------------------+-------------------+---------
+ dummy      | foreign_data_user | -       | -                        |                   | 
+ foo        | foreign_data_user | -       | -                        |                   | 
+ postgresql | foreign_data_user | -       | postgresql_fdw_validator |                   | 
 (3 rows)
 
 \des+
@@ -250,11 +250,11 @@ NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to server s1
 drop cascades to user mapping for foreign_data_user
 \dew+
-                              List of foreign-data wrappers
-    Name    |       Owner       |        Validator         | Access privileges | Options 
-------------+-------------------+--------------------------+-------------------+---------
- dummy      | foreign_data_user | -                        |                   | 
- postgresql | foreign_data_user | postgresql_fdw_validator |                   | 
+                                   List of foreign-data wrappers
+    Name    |       Owner       | Handler |        Validator         | Access privileges | Options 
+------------+-------------------+---------+--------------------------+-------------------+---------
+ dummy      | foreign_data_user | -       | -                        |                   | 
+ postgresql | foreign_data_user | -       | postgresql_fdw_validator |                   | 
 (2 rows)
 
 \des+
@@ -669,6 +669,10 @@ Has OIDs: no
 
 CREATE INDEX id_ft1_c2 ON ft1 (c2);                             -- ERROR
 ERROR:  "ft1" is not a table
+SELECT * FROM ft1;                                              -- ERROR
+ERROR:  foreign-data wrapper has no handler
+EXPLAIN SELECT * FROM ft1;                                      -- ERROR
+ERROR:  foreign-data wrapper has no handler
 -- ALTER FOREIGN TABLE
 COMMENT ON FOREIGN TABLE ft1 IS 'foreign table';
 COMMENT ON FOREIGN TABLE ft1 IS NULL;
@@ -1105,9 +1109,9 @@ NOTICE:  drop cascades to server sc
 \c
 DROP ROLE foreign_data_user;
 -- At this point we should have no wrappers, no servers, and no mappings.
-SELECT fdwname, fdwvalidator, fdwoptions FROM pg_foreign_data_wrapper;
- fdwname | fdwvalidator | fdwoptions 
----------+--------------+------------
+SELECT fdwname, fdwvalidator, fdwhandler, fdwoptions FROM pg_foreign_data_wrapper;
+ fdwname | fdwvalidator | fdwhandler | fdwoptions 
+---------+--------------+------------+------------
 (0 rows)
 
 SELECT srvname, srvoptions FROM pg_foreign_server;
diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql
index 86b698a..2e0fa9a 100644
--- a/src/test/regress/sql/foreign_data.sql
+++ b/src/test/regress/sql/foreign_data.sql
@@ -24,7 +24,7 @@ CREATE FOREIGN DATA WRAPPER dummy;
 CREATE FOREIGN DATA WRAPPER postgresql VALIDATOR postgresql_fdw_validator;
 
 -- At this point we should have 2 built-in wrappers and no servers.
-SELECT fdwname, fdwvalidator::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3;
+SELECT fdwname, fdwvalidator::regproc, fdwhandler::regproc, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3;
 SELECT srvname, srvoptions FROM pg_foreign_server;
 SELECT * FROM pg_user_mapping;
 
@@ -271,6 +271,8 @@ COMMENT ON COLUMN ft1.c1 IS 'ft1.c1';
 \d+ ft1
 \det+
 CREATE INDEX id_ft1_c2 ON ft1 (c2);                             -- ERROR
+SELECT * FROM ft1;                                              -- ERROR
+EXPLAIN SELECT * FROM ft1;                                      -- ERROR
 
 -- ALTER FOREIGN TABLE
 COMMENT ON FOREIGN TABLE ft1 IS 'foreign table';
@@ -453,6 +455,6 @@ DROP FOREIGN DATA WRAPPER dummy CASCADE;
 DROP ROLE foreign_data_user;
 
 -- At this point we should have no wrappers, no servers, and no mappings.
-SELECT fdwname, fdwvalidator, fdwoptions FROM pg_foreign_data_wrapper;
+SELECT fdwname, fdwvalidator, fdwhandler, fdwoptions FROM pg_foreign_data_wrapper;
 SELECT srvname, srvoptions FROM pg_foreign_server;
 SELECT * FROM pg_user_mapping;
