diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f5f26d035d9088b3fbd639dba9c0f5e9e50a1bb2..52be7a4d26dcd13910a9f3296681862162b66d8c 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.184 2008/12/18 18:20:33 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.185 2008/12/19 16:25:16 petere Exp $ -->
 <!--
  Documentation of the system catalogs, directed toward PostgreSQL developers
  -->
@@ -133,6 +133,16 @@
       <entry>enum label and value definitions</entry>
      </row>
 
+     <row>
+      <entry><link linkend="catalog-pg-foreign-data-wrapper"><structname>pg_foreign_data_wrapper</structname></link></entry>
+      <entry>foreign-data wrapper definitions</entry>
+     </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-foreign-server"><structname>pg_foreign_server</structname></link></entry>
+      <entry>foreign server definitions</entry>
+     </row>
+
      <row>
       <entry><link linkend="catalog-pg-index"><structname>pg_index</structname></link></entry>
       <entry>additional index information</entry>
@@ -247,6 +257,11 @@
       <entry><link linkend="catalog-pg-type"><structname>pg_type</structname></link></entry>
       <entry>data types</entry>
      </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link></entry>
+      <entry>mappings of users to foreign servers</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -2552,6 +2567,229 @@
  </sect1>
 
 
+ <sect1 id="catalog-pg-foreign-data-wrapper">
+  <title><structname>pg_foreign_data_wrapper</structname></title>
+
+  <indexterm zone="catalog-pg-foreign-data-wrapper">
+   <primary>pg_foreign_data_wrapper</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_foreign_data_wrapper</structname> stores
+   foreign-data wrapper definitions.  A foreign-data wrapper is the
+   mechanism by which external data, residing on foreign servers, is
+   accessed.
+  </para>
+
+  <table>
+   <title><structname>pg_foreign_data_wrapper</> Columns</title>
+
+   <tgroup cols=4>
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><structfield>fdwname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>Name of the foreign-data wrapper</entry>
+     </row>
+
+     <row>
+      <entry><structfield>fdwowner</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.oid</literal></entry>
+      <entry>Owner of the foreign-data wrapper</entry>
+     </row>
+
+     <row>
+      <entry><structfield>fdwlibrary</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>File name of the library implementing this foreign-data wrapper</entry>
+     </row>
+
+     <row>
+      <entry><structfield>fdwacl</structfield></entry>
+      <entry><type>aclitem[]</type></entry>
+      <entry></entry>
+      <entry>
+       Access privileges; see
+       <xref linkend="sql-grant" endterm="sql-grant-title"> and
+       <xref linkend="sql-revoke" endterm="sql-revoke-title">
+       for details
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>fdwoptions</structfield></entry>
+      <entry><type>text[]</type></entry>
+      <entry></entry>
+      <entry>
+       Foreign-data wrapper specific options, as <quote>keyword=value</> strings
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+
+ <sect1 id="catalog-pg-foreign-server">
+  <title><structname>pg_foreign_server</structname></title>
+
+  <indexterm zone="catalog-pg-foreign-server">
+   <primary>pg_foreign_server</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_foreign_server</structname> stores
+   foreign server definitions.  A foreign server describes the
+   connection to a remote server, managing external data.  Foreign
+   servers are accessed via foreign-data wrappers.
+  </para>
+
+  <table>
+   <title><structname>pg_foreign_server</> Columns</title>
+
+   <tgroup cols=4>
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><structfield>srvname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>Name of the foreign server</entry>
+     </row>
+
+     <row>
+      <entry><structfield>srvowner</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.oid</literal></entry>
+      <entry>Owner of the foreign server</entry>
+     </row>
+
+     <row>
+      <entry><structfield>srvfdw</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-foreign-data-wrapper"><structname>pg_foreign_data_wrapper</structname></link>.oid</literal></entry>
+      <entry>The OID of the foreign-data wrapper of this foreign server</entry>
+     </row>
+
+     <row>
+      <entry><structfield>srvtype</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>Type of the server (optional)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>srvversion</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>Version of the server (optional)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>srvacl</structfield></entry>
+      <entry><type>aclitem[]</type></entry>
+      <entry></entry>
+      <entry>
+       Access privileges; see
+       <xref linkend="sql-grant" endterm="sql-grant-title"> and
+       <xref linkend="sql-revoke" endterm="sql-revoke-title">
+       for details
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>srvoptions</structfield></entry>
+      <entry><type>text[]</type></entry>
+      <entry></entry>
+      <entry>
+       Foreign server specific options, as <quote>keyword=value</> strings.
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+
+ <sect1 id="catalog-pg-user-mapping">
+  <title><structname>pg_user_mapping</structname></title>
+
+  <indexterm zone="catalog-pg-user-mapping">
+   <primary>pg_user_mapping</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_user_mapping</structname> stores
+   the mappings from local user to remote.  Access to this catalog is
+   restricted from normal users, use the view
+   <link linkend="view-pg-user-mappings"><structname>pg_user_mappings</structname></link>
+   instead.
+  </para>
+
+  <table>
+   <title><structname>pg_user_mapping</> Columns</title>
+
+   <tgroup cols=4>
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><structfield>umuser</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.oid</literal></entry>
+      <entry>OID of the local role being mapped, 0 if the user mapping is public</entry>
+     </row>
+
+     <row>
+      <entry><structfield>umserver</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-foreign-server"><structname>pg_foreign_server</structname></link>.oid</literal></entry>
+      <entry>
+       The OID of the foreign server that contains this mapping
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>umoptions</structfield></entry>
+      <entry><type>text[]</type></entry>
+      <entry></entry>
+      <entry>
+       User mapping specific options, as <quote>keyword=value</> strings.
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+
  <sect1 id="catalog-pg-index">
   <title><structname>pg_index</structname></title>
 
@@ -7019,6 +7257,91 @@
 
  </sect1>
 
+ <sect1 id="view-pg-user-mappings">
+  <title><structname>pg_user_mappings</structname></title>
+
+  <indexterm zone="view-pg-user-mappings">
+   <primary>pg_user_mappings</primary>
+  </indexterm>
+
+  <para>
+   The view <structname>pg_user_mappings</structname> provides access
+   to information about user mappings.  This is essentially a publicly
+   readable view of
+   <link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link>
+   that leaves out the options field if the user has no rights to use
+   it.
+  </para>
+
+  <table>
+   <title><structname>pg_user_mappings</> Columns</title>
+
+   <tgroup cols=3>
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><structfield>umid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-user-mapping"><structname>pg_user_mapping</structname></link>.oid</literal></entry>
+      <entry>OID of the user mapping</entry>
+     </row>
+
+     <row>
+      <entry><structfield>srvid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-foreign-server"><structname>pg_foreign_server</structname></link>.oid</literal></entry>
+      <entry>
+       The OID of the foreign server that contains this mapping
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>srvname</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>
+       Name of the foreign server
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>umuser</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.oid</literal></entry>
+      <entry>OID of the local role being mapped, 0 if the user mapping is public</entry>
+     </row>
+
+     <row>
+      <entry><structfield>usename</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>Name of the local user to be mapped</entry>
+     </row>
+
+     <row>
+      <entry><structfield>umoptions</structfield></entry>
+      <entry><type>text[]</type></entry>
+      <entry></entry>
+      <entry>
+       User mapping specific options, as <quote>keyword=value</>
+       strings, if the current user is the owner of the foreign
+       server, else null.
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+
  <sect1 id="view-pg-views">
   <title><structname>pg_views</structname></title>
 
diff --git a/doc/src/sgml/features.sgml b/doc/src/sgml/features.sgml
index 712ad4e4ecf207578f72b48ccdd062eabdebc042..56b5be3318307307a29834fcc5ffa069283201d8 100644
--- a/doc/src/sgml/features.sgml
+++ b/doc/src/sgml/features.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/features.sgml,v 2.29 2008/11/27 12:12:02 petere Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/features.sgml,v 2.30 2008/12/19 16:25:16 petere Exp $ -->
 
 <appendix id="features">
  <title>SQL Conformance</title>
@@ -71,11 +71,11 @@
  </para>
 
  <para>
-  The <productname>PostgreSQL</productname> core covers parts 1, 2,
+  The <productname>PostgreSQL</productname> core covers parts 1, 2, 9,
   11, and 14.  Part 3 is covered by the ODBC driver, and part 13 is
   covered by the PL/Java plug-in, but exact conformance is currently
   not being verified for these components.  There are currently no
-  implementations of parts 4, 9, and 10
+  implementations of parts 4 and 10
   for <productname>PostgreSQL</productname>.
  </para>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c08c4801882c5929581c8e1128dba969bbc9edd2..de50c0e1d5691168ee639a176f127df764611fc8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.462 2008/12/18 18:20:33 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.463 2008/12/19 16:25:16 petere Exp $ -->
 
  <chapter id="functions">
   <title>Functions and Operators</title>
@@ -11335,6 +11335,21 @@ SET search_path TO <replaceable>schema</> <optional>, <replaceable>schema</>, ..
        <entry><type>boolean</type></entry>
        <entry>does current user have privilege for database</entry>
       </row>
+      <row>
+       <entry><literal><function>has_foreign_data_wrapper_privilege</function>(<parameter>user</parameter>,
+                                  <parameter>fdw</parameter>,
+                                  <parameter>privilege</parameter>)</literal>
+       </entry>
+       <entry><type>boolean</type></entry>
+       <entry>does user have privilege for foreign-data wrapper</entry>
+      </row>
+      <row>
+       <entry><literal><function>has_foreign_data_wrapper_privilege</function>(<parameter>fdw</parameter>,
+                                  <parameter>privilege</parameter>)</literal>
+       </entry>
+       <entry><type>boolean</type></entry>
+       <entry>does current user have privilege for foreign-data wrapper</entry>
+      </row>
       <row>
        <entry><literal><function>has_function_privilege</function>(<parameter>user</parameter>,
                                   <parameter>function</parameter>,
@@ -11380,6 +11395,21 @@ SET search_path TO <replaceable>schema</> <optional>, <replaceable>schema</>, ..
        <entry><type>boolean</type></entry>
        <entry>does current user have privilege for schema</entry>
       </row>
+      <row>
+       <entry><literal><function>has_server_privilege</function>(<parameter>user</parameter>,
+                                  <parameter>server</parameter>,
+                                  <parameter>privilege</parameter>)</literal>
+       </entry>
+       <entry><type>boolean</type></entry>
+       <entry>does user have privilege for foreign server</entry>
+      </row>
+      <row>
+       <entry><literal><function>has_server_privilege</function>(<parameter>server</parameter>,
+                                  <parameter>privilege</parameter>)</literal>
+       </entry>
+       <entry><type>boolean</type></entry>
+       <entry>does current user have privilege for foreign server</entry>
+      </row>
       <row>
        <entry><literal><function>has_table_privilege</function>(<parameter>user</parameter>,
                                   <parameter>table</parameter>,
@@ -11435,12 +11465,18 @@ SET search_path TO <replaceable>schema</> <optional>, <replaceable>schema</>, ..
    <indexterm>
     <primary>has_function_privilege</primary>
    </indexterm>
+   <indexterm>
+    <primary>has_foreign_data_wrapper_privilege</primary>
+   </indexterm>
    <indexterm>
     <primary>has_language_privilege</primary>
    </indexterm>
    <indexterm>
     <primary>has_schema_privilege</primary>
    </indexterm>
+   <indexterm>
+    <primary>has_server_privilege</primary>
+   </indexterm>
    <indexterm>
     <primary>has_table_privilege</primary>
    </indexterm>
@@ -11478,6 +11514,14 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
 </programlisting>
    </para>
 
+   <para>
+    <function>has_foreign_data_wrapper_privilege</function> checks whether a user
+    can access a foreign-data wrapper in a particular way.  The possibilities for its
+    arguments are analogous to <function>has_table_privilege</function>.
+    The desired access privilege type must evaluate to
+    <literal>USAGE</literal>.
+   </para>
+
    <para>
     <function>has_language_privilege</function> checks whether a user
     can access a procedural language in a particular way.  The possibilities
@@ -11495,6 +11539,14 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
     <literal>USAGE</literal>.
    </para>
 
+   <para>
+    <function>has_server_privilege</function> checks whether a user
+    can access a foreign server in a particular way.  The possibilities for its
+    arguments are analogous to <function>has_table_privilege</function>.
+    The desired access privilege type must evaluate to
+    <literal>USAGE</literal>.
+   </para>
+
    <para>
     <function>has_table_privilege</function> checks whether a user
     can access a table in a particular way.  The user can be
diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml
index da01c9cc5a71085eaf2dbb8ce85e251543393465..f645c1252d625d680c409e86bff09f40f9e2fad3 100644
--- a/doc/src/sgml/information_schema.sgml
+++ b/doc/src/sgml/information_schema.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/information_schema.sgml,v 1.35 2008/11/25 20:47:42 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/information_schema.sgml,v 1.36 2008/12/19 16:25:16 petere Exp $ -->
 
 <chapter id="information-schema">
  <title>The Information Schema</title>
@@ -2145,6 +2145,237 @@ ORDER BY c.ordinal_position;
   </table>
  </sect1>
 
+ <sect1 id="infoschema-foreign-data-wrapper-options">
+  <title><literal>foreign_data_wrapper_options</literal></title>
+
+  <para>
+   The view <literal>foreign_data_wrapper_options</literal> contains
+   all the options defined for foreign-data wrappers in the current
+   database.  Only those foreign-data wrappers are shown that the
+   current user has access to (by way of being the owner or having
+   some privilege).
+  </para>
+
+  <table>
+   <title><literal>foreign_data_wrapper_options</literal> Columns</title>
+
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Data Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><literal>foreign_data_wrapper_catalog</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the database that the foreign-data wrapper is defined in (always the current database)</entry>
+     </row>
+
+     <row>
+      <entry><literal>foreign_data_wrapper_name</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the foreign-data wrapper</entry>
+     </row>
+
+     <row>
+      <entry><literal>option_name</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of an option</entry>
+     </row>
+
+     <row>
+      <entry><literal>option_value</literal></entry>
+      <entry><type>character_data</type></entry>
+      <entry>Value of the option</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-foreign-data-wrappers">
+  <title><literal>foreign_data_wrappers</literal></title>
+
+  <para>
+   The view <literal>foreign_data_wrappers</literal> contains all
+   foreign-data wrappers defined in the current database.  Only those
+   foreign-data wrappers are shown that the current user has access to
+   (by way of being the owner or having some privilege).
+  </para>
+
+  <table>
+   <title><literal>foreign_data_wrappers</literal> Columns</title>
+
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Data Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><literal>foreign_data_wrapper_catalog</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the database that contains the foreign-data
+      wrapper (always the current database)</entry>
+     </row>
+
+     <row>
+      <entry><literal>foreign_data_wrapper_name</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the foreign-data wrapper</entry>
+     </row>
+
+     <row>
+      <entry><literal>authorization_identifier</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the owner of the foreign server</entry>
+     </row>
+
+     <row>
+      <entry><literal>library_name</literal></entry>
+      <entry><type>character_data</type></entry>
+      <entry>File name of the library that implementing this foreign-data wrapper</entry>
+     </row>
+
+     <row>
+      <entry><literal>foreign_data_wrapper_language</literal></entry>
+      <entry><type>character_data</type></entry>
+      <entry>Language used to implement this foreign-data wrapper</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-foreign-server-options">
+  <title><literal>foreign_server_options</literal></title>
+
+  <para>
+   The view <literal>foreign_server_options</literal> contains all the
+   options defined for foreign servers in the current database.  Only
+   those foreign servers are shown that the current user has access to
+   (by way of being the owner or having some privilege).
+  </para>
+
+  <table>
+   <title><literal>foreign_server_options</literal> Columns</title>
+
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Data Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><literal>foreign_server_catalog</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the database that the foreign server is defined in (always the current database)</entry>
+     </row>
+
+     <row>
+      <entry><literal>foreign_server_name</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the foreign server</entry>
+     </row>
+
+     <row>
+      <entry><literal>option_name</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of an option</entry>
+     </row>
+
+     <row>
+      <entry><literal>option_value</literal></entry>
+      <entry><type>character_data</type></entry>
+      <entry>Value of the option</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-foreign-servers">
+  <title><literal>foreign_servers</literal></title>
+
+  <para>
+   The view <literal>foreign_servers</literal> contains all foreign
+   servers defined in the current database.  Only those foreign
+   servers are shown that the current user has access to (by way of
+   being the owner or having some privilege).
+  </para>
+
+  <table>
+   <title><literal>foreign_servers</literal> Columns</title>
+
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Data Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><literal>foreign_server_catalog</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the database that the foreign server is defined in (always the current database)</entry>
+     </row>
+
+     <row>
+      <entry><literal>foreign_server_name</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the foreign server</entry>
+     </row>
+
+     <row>
+      <entry><literal>foreign_data_wrapper_catalog</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the database that contains the foreign-data
+      wrapper used by the foreign server (always the current database)</entry>
+     </row>
+
+     <row>
+      <entry><literal>foreign_data_wrapper_name</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the foreign-data wrapper used by the foreign server</entry>
+     </row>
+
+     <row>
+      <entry><literal>foreign_server_type</literal></entry>
+      <entry><type>character_data</type></entry>
+      <entry>Foreign server type information, if specified upon creation</entry>
+     </row>
+
+     <row>
+      <entry><literal>foreign_server_version</literal></entry>
+      <entry><type>character_data</type></entry>
+      <entry>Foreign server version information, if specified upon creation</entry>
+     </row>
+
+     <row>
+      <entry><literal>authorization_identifier</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the owner of the foreign server</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="infoschema-key-column-usage">
   <title><literal>key_column_usage</literal></title>
 
@@ -2848,15 +3079,11 @@ ORDER BY c.ordinal_position;
   <title><literal>role_usage_grants</literal></title>
 
   <para>
-   The view <literal>role_usage_grants</literal> is meant to identify
+   The view <literal>role_usage_grants</literal> identifies
    <literal>USAGE</literal> privileges granted on various kinds of
-   objects to a currently enabled role or by a currently enabled role.
-   In <productname>PostgreSQL</productname>, this currently only
-   applies to domains, and since domains do not have real privileges
-   in <productname>PostgreSQL</productname>, this view is empty.
+   objects where the grantor or grantee is a currently enabled role.
    Further information can be found under
-   <literal>usage_privileges</literal>.  In the future, this view might
-   contain more useful information.
+   <literal>usage_privileges</literal>.
   </para>
 
   <table>
@@ -2875,13 +3102,13 @@ ORDER BY c.ordinal_position;
      <row>
       <entry><literal>grantor</literal></entry>
       <entry><type>sql_identifier</type></entry>
-      <entry>In the future, the name of the role that granted the privilege</entry>
+      <entry>The name of the role that granted the privilege</entry>
      </row>
 
      <row>
       <entry><literal>grantee</literal></entry>
       <entry><type>sql_identifier</type></entry>
-      <entry>In the future, the name of the role that the privilege was granted to</entry>
+      <entry>The name of the role that the privilege was granted to</entry>
      </row>
 
      <row>
@@ -2893,7 +3120,8 @@ ORDER BY c.ordinal_position;
      <row>
       <entry><literal>object_schema</literal></entry>
       <entry><type>sql_identifier</type></entry>
-      <entry>Name of the schema containing the object</entry>
+      <entry>Name of the schema containing the object, if applicable,
+      else an empty string</entry>
      </row>
 
      <row>
@@ -2905,7 +3133,7 @@ ORDER BY c.ordinal_position;
      <row>
       <entry><literal>object_type</literal></entry>
       <entry><type>character_data</type></entry>
-      <entry>In the future, the type of the object</entry>
+      <entry><literal>DOMAIN</literal> or <literal>FOREIGN DATA WRAPPER</literal> or <literal>FOREIGN SERVER</literal></entry>
      </row>
 
      <row>
@@ -4718,15 +4946,20 @@ ORDER BY c.ordinal_position;
   <title><literal>usage_privileges</literal></title>
 
   <para>
-   The view <literal>usage_privileges</literal> is meant to identify
+   The view <literal>usage_privileges</literal> identifies
    <literal>USAGE</literal> privileges granted on various kinds of
    objects to a currently enabled role or by a currently enabled role.
-   In <productname>PostgreSQL</productname>, this currently only
-   applies to domains, and since domains do not have real privileges
+   In <productname>PostgreSQL</productname>, this currently applies to
+   domains, foreign-data wrappers, and foreign servers.  There is one
+   row for each combination of object, grantor, and grantee.
+  </para>
+
+  <para>
+   Since domains do not have real privileges
    in <productname>PostgreSQL</productname>, this view shows implicit
-   <literal>USAGE</literal> privileges granted to
-   <literal>PUBLIC</literal> for all domains.  In the future, this
-   view might contain more useful information.
+   non-grantable <literal>USAGE</literal> privileges granted by the
+   owner to <literal>PUBLIC</literal> for all domains.  The other
+   object types, however, show real privileges.
   </para>
 
   <table>
@@ -4745,13 +4978,13 @@ ORDER BY c.ordinal_position;
      <row>
       <entry><literal>grantor</literal></entry>
       <entry><type>sql_identifier</type></entry>
-      <entry>Currently set to the name of the owner of the object</entry>
+      <entry>Name of the role that granted the privilege</entry>
      </row>
 
      <row>
       <entry><literal>grantee</literal></entry>
       <entry><type>sql_identifier</type></entry>
-      <entry>Currently always <literal>PUBLIC</literal></entry>
+      <entry>Name of the role that the privilege was granted to</entry>
      </row>
 
      <row>
@@ -4763,7 +4996,8 @@ ORDER BY c.ordinal_position;
      <row>
       <entry><literal>object_schema</literal></entry>
       <entry><type>sql_identifier</type></entry>
-      <entry>Name of the schema containing the object</entry>
+      <entry>Name of the schema containing the object, if applicable,
+      else an empty string</entry>
      </row>
 
      <row>
@@ -4775,7 +5009,7 @@ ORDER BY c.ordinal_position;
      <row>
       <entry><literal>object_type</literal></entry>
       <entry><type>character_data</type></entry>
-      <entry>Currently always <literal>DOMAIN</literal></entry>
+      <entry><literal>DOMAIN</literal> or <literal>FOREIGN DATA WRAPPER</literal> or <literal>FOREIGN SERVER</literal></entry>
      </row>
 
      <row>
@@ -4787,7 +5021,115 @@ ORDER BY c.ordinal_position;
      <row>
       <entry><literal>is_grantable</literal></entry>
       <entry><type>character_data</type></entry>
-      <entry>Currently always <literal>NO</literal></entry>
+      <entry><literal>YES</literal> if the privilege is grantable, <literal>NO</literal> if not</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-user-mapping-options">
+  <title><literal>user_mapping_options</literal></title>
+
+  <para>
+   The view <literal>user_mapping_options</literal> contains all the
+   options defined for user mappings in the current database.  Only
+   those user mappings are shown where the current user has access to
+   the corresponding foreign server (by way of being the owner or
+   having some privilege).
+  </para>
+
+  <table>
+   <title><literal>user_mapping_options</literal> Columns</title>
+
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Data Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><literal>authorization_identifier</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the user being mapped,
+      or <literal>PUBLIC</literal> if the mapping is public</entry>
+     </row>
+
+     <row>
+      <entry><literal>foreign_server_catalog</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the database that the foreign server used by this
+      mapping is defined in (always the current database)</entry>
+     </row>
+
+     <row>
+      <entry><literal>foreign_server_name</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the foreign server used by this mapping</entry>
+     </row>
+
+     <row>
+      <entry><literal>option_name</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of an option</entry>
+     </row>
+
+     <row>
+      <entry><literal>option_value</literal></entry>
+      <entry><type>character_data</type></entry>
+      <entry>Value of the option</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-user-mappings">
+  <title><literal>user_mappings</literal></title>
+
+  <para>
+   The view <literal>user_mappings</literal> contains all user
+   mappings defined in the current database.  Only those user mappings
+   are shown where the current user has access to the corresponding
+   foreign server (by way of being the owner or having some
+   privilege).
+  </para>
+
+  <table>
+   <title><literal>user_mappings</literal> Columns</title>
+
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Data Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><literal>authorization_identifier</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the user being mapped,
+      or <literal>PUBLIC</literal> if the mapping is public</entry>
+     </row>
+
+     <row>
+      <entry><literal>foreign_server_catalog</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the database that the foreign server used by this
+      mapping is defined in (always the current database)</entry>
+     </row>
+
+     <row>
+      <entry><literal>foreign_server_name</literal></entry>
+      <entry><type>sql_identifier</type></entry>
+      <entry>Name of the foreign server used by this mapping</entry>
      </row>
     </tbody>
    </tgroup>
diff --git a/doc/src/sgml/keywords.sgml b/doc/src/sgml/keywords.sgml
index de3bedfdcbe94aaf9fd8569211c141c14d52785e..f7f7d649563162c7e1a04b7d8e3cdb63daa66811 100644
--- a/doc/src/sgml/keywords.sgml
+++ b/doc/src/sgml/keywords.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/keywords.sgml,v 2.21 2008/12/03 12:39:57 petere Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/keywords.sgml,v 2.22 2008/12/19 16:25:16 petere Exp $ -->
 
 <appendix id="sql-keywords-appendix">
  <title><acronym>SQL</acronym> Key Words</title>
@@ -2687,6 +2687,14 @@
     <entry>reserved</entry>
     <entry>reserved</entry>
    </row>
+   <row>
+    <entry><token>LIBRARY</token></entry>
+    <entry>non-reserved</entry>
+    <entry>non-reserved</entry>
+    <entry>non-reserved</entry>
+    <entry></entry>
+    <entry></entry>
+   </row>
    <row>
     <entry><token>LIKE</token></entry>
     <entry>reserved (can be function or type)</entry>
@@ -2818,8 +2826,8 @@
    <row>
     <entry><token>MAPPING</token></entry>
     <entry>non-reserved</entry>
-    <entry></entry>
-    <entry></entry>
+    <entry>non-reserved</entry>
+    <entry>non-reserved</entry>
     <entry></entry>
     <entry></entry>
    </row>
@@ -3441,7 +3449,7 @@
    </row>
    <row>
     <entry><token>OPTIONS</token></entry>
-    <entry></entry>
+    <entry>non-reserved</entry>
     <entry>non-reserved</entry>
     <entry>non-reserved</entry>
     <entry>non-reserved</entry>
@@ -4455,6 +4463,14 @@
     <entry>non-reserved</entry>
     <entry>non-reserved</entry>
    </row>
+   <row>
+    <entry><token>SERVER</token></entry>
+    <entry>non-reserved</entry>
+    <entry>non-reserved</entry>
+    <entry>non-reserved</entry>
+    <entry></entry>
+    <entry></entry>
+   </row>
    <row>
     <entry><token>SERVER_NAME</token></entry>
     <entry></entry>
@@ -5583,6 +5599,14 @@
     <entry>reserved</entry>
     <entry>reserved</entry>
    </row>
+   <row>
+    <entry><token>WRAPPER</token></entry>
+    <entry>non-reserved</entry>
+    <entry>non-reserved</entry>
+    <entry>non-reserved</entry>
+    <entry></entry>
+    <entry></entry>
+   </row>
    <row>
     <entry><token>WRITE</token></entry>
     <entry>non-reserved</entry>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index b80a2cf8f04895372b5b20c54490f49b72bac88f..6c20b623c49ad8f866edab0b3256de0b736fd639 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/allfiles.sgml,v 1.73 2008/03/27 17:24:16 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/allfiles.sgml,v 1.74 2008/12/19 16:25:16 petere Exp $
 PostgreSQL documentation
 Complete list of usable sgml source files in this directory.
 -->
@@ -10,6 +10,7 @@ Complete list of usable sgml source files in this directory.
 <!entity alterConversion    system "alter_conversion.sgml">
 <!entity alterDatabase      system "alter_database.sgml">
 <!entity alterDomain        system "alter_domain.sgml">
+<!entity alterForeignDataWrapper system "alter_foreign_data_wrapper.sgml">
 <!entity alterFunction      system "alter_function.sgml">
 <!entity alterGroup         system "alter_group.sgml">
 <!entity alterIndex         system "alter_index.sgml">
@@ -19,6 +20,7 @@ Complete list of usable sgml source files in this directory.
 <!entity alterOperatorFamily system "alter_opfamily.sgml">
 <!entity alterRole          system "alter_role.sgml">
 <!entity alterSchema        system "alter_schema.sgml">
+<!entity alterServer        system "alter_server.sgml">
 <!entity alterSequence      system "alter_sequence.sgml">
 <!entity alterTable         system "alter_table.sgml">
 <!entity alterTableSpace    system "alter_tablespace.sgml">
@@ -29,6 +31,7 @@ Complete list of usable sgml source files in this directory.
 <!entity alterTrigger       system "alter_trigger.sgml">
 <!entity alterType          system "alter_type.sgml">
 <!entity alterUser          system "alter_user.sgml">
+<!entity alterUserMapping   system "alter_user_mapping.sgml">
 <!entity alterView          system "alter_view.sgml">
 <!entity analyze            system "analyze.sgml">
 <!entity begin              system "begin.sgml">
@@ -45,6 +48,7 @@ Complete list of usable sgml source files in this directory.
 <!entity createConversion   system "create_conversion.sgml">
 <!entity createDatabase     system "create_database.sgml">
 <!entity createDomain       system "create_domain.sgml">
+<!entity createForeignDataWrapper system "create_foreign_data_wrapper.sgml">
 <!entity createFunction     system "create_function.sgml">
 <!entity createGroup        system "create_group.sgml">
 <!entity createIndex        system "create_index.sgml">
@@ -56,6 +60,7 @@ Complete list of usable sgml source files in this directory.
 <!entity createRule         system "create_rule.sgml">
 <!entity createSchema       system "create_schema.sgml">
 <!entity createSequence     system "create_sequence.sgml">
+<!entity createServer       system "create_server.sgml">
 <!entity createTable        system "create_table.sgml">
 <!entity createTableAs      system "create_table_as.sgml">
 <!entity createTableSpace   system "create_tablespace.sgml">
@@ -66,6 +71,7 @@ Complete list of usable sgml source files in this directory.
 <!entity createTSTemplate   system "create_tstemplate.sgml">
 <!entity createType         system "create_type.sgml">
 <!entity createUser         system "create_user.sgml">
+<!entity createUserMapping  system "create_user_mapping.sgml">
 <!entity createView         system "create_view.sgml">
 <!entity deallocate         system "deallocate.sgml">
 <!entity declare            system "declare.sgml">
@@ -76,6 +82,7 @@ Complete list of usable sgml source files in this directory.
 <!entity dropConversion     system "drop_conversion.sgml">
 <!entity dropDatabase       system "drop_database.sgml">
 <!entity dropDomain         system "drop_domain.sgml">
+<!entity dropForeignDataWrapper system "drop_foreign_data_wrapper.sgml">
 <!entity dropFunction       system "drop_function.sgml">
 <!entity dropGroup          system "drop_group.sgml">
 <!entity dropIndex          system "drop_index.sgml">
@@ -88,6 +95,7 @@ Complete list of usable sgml source files in this directory.
 <!entity dropRule           system "drop_rule.sgml">
 <!entity dropSchema         system "drop_schema.sgml">
 <!entity dropSequence       system "drop_sequence.sgml">
+<!entity dropServer         system "drop_server.sgml">
 <!entity dropTable          system "drop_table.sgml">
 <!entity dropTableSpace     system "drop_tablespace.sgml">
 <!entity dropTrigger        system "drop_trigger.sgml">
@@ -97,6 +105,7 @@ Complete list of usable sgml source files in this directory.
 <!entity dropTSTemplate     system "drop_tstemplate.sgml">
 <!entity dropType           system "drop_type.sgml">
 <!entity dropUser           system "drop_user.sgml">
+<!entity dropUserMapping    system "drop_user_mapping.sgml">
 <!entity dropView           system "drop_view.sgml">
 <!entity end                system "end.sgml">
 <!entity execute            system "execute.sgml">
diff --git a/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml b/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..877c475517064f4eaa421b47740bde1629c272cc
--- /dev/null
+++ b/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml
@@ -0,0 +1,132 @@
+<!--
+$PostgreSQL: pgsql/doc/src/sgml/ref/alter_foreign_data_wrapper.sgml,v 1.1 2008/12/19 16:25:16 petere Exp $
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-ALTERFOREIGNDATAWRAPPER">
+ <refmeta>
+  <refentrytitle id="sql-alterforeigndatawrapper-title">ALTER FOREIGN DATA WRAPPER</refentrytitle>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER FOREIGN DATA WRAPPER</refname>
+  <refpurpose>change the definition of a foreign-data wrapper</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-alterforeigndatawrapper">
+  <primary>ALTER FOREIGN DATA WRAPPER</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable>
+    [ LIBRARY '<replaceable class="parameter">libraryname</replaceable>' ]
+    [ 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>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER FOREIGN DATA WRAPPER</command> changes the
+   definition of a foreign-data wrapper.  The first form of the
+   command changes the library or the generic options of the
+   foreign-data wrapper (at least one clause is required).  The second
+   form changes the owner of the foreign-data wrapper.
+  </para>
+
+  <para>
+   Only superusers can alter foreign-data wrappers.  Additionally,
+   only superusers can own foreign-data wrappers.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing foreign-data wrapper.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">libraryname</replaceable></term>
+    <listitem>
+     <para>
+      New name of the foreign-data wrapper library.
+     </para>
+
+     <para>
+      Note that it is possible that after changing the library, the
+      options to the foreign-data wrapper, servers, and user mappings
+      have become invalid.   It is up to the user to make sure that
+      these options are correct before using the foreign-data
+      wrapper.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
+    <listitem>
+     <para>
+      Change options for the foreign-data
+      wrapper.  <literal>ADD</>, <literal>SET</>, and <literal>DROP</>
+      specify the action to be performed.  <literal>ADD</> is assumed
+      if no operation is explicitly specified.  Option names must be
+      unique; names and values are also validated using the foreign
+      data wrapper library.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Change a foreign-data wrapper <literal>dbi</>, add
+   option <literal>foo</>, drop <literal>bar</>:
+<programlisting>
+ALTER FOREIGN DATA WRAPPER dbi OPTIONS (ADD foo '1', DROP 'bar');
+</programlisting>
+  </para>
+
+  <para>
+   Change the foreign-data wrapper <literal>dbi</> library
+   to <literal>/home/bob/mylibrary.so</>:
+<programlisting>
+ALTER FOREIGN DATA WRAPPER dbi LIBRARY '/home/bob/mylibrary.so';
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>ALTER FOREIGN DATA WRAPPER</command> conforms to ISO/IEC
+   9075-9 (SQL/MED).  The standard does not specify the <literal>OWNER
+   TO</> variant of the command.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createforeigndatawrapper" endterm="sql-createforeigndatawrapper-title"></member>
+   <member><xref linkend="sql-dropforeigndatawrapper" endterm="sql-dropforeigndatawrapper-title"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/alter_server.sgml b/doc/src/sgml/ref/alter_server.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..0595bca63bb1de7e091ada19a338e68c891f0b6f
--- /dev/null
+++ b/doc/src/sgml/ref/alter_server.sgml
@@ -0,0 +1,123 @@
+<!--
+$PostgreSQL: pgsql/doc/src/sgml/ref/alter_server.sgml,v 1.1 2008/12/19 16:25:16 petere Exp $
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-ALTERSERVER">
+ <refmeta>
+  <refentrytitle id="sql-alterserver-title">ALTER SERVER</refentrytitle>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER SERVER</refname>
+  <refpurpose>change the definition of a foreign server</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-alterserver">
+  <primary>ALTER SERVER</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER SERVER <replaceable class="parameter">servername</replaceable> [ VERSION 'newversion' ]
+    [ OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] ) ]
+ALTER SERVER <replaceable class="PARAMETER">servername</replaceable> OWNER TO <replaceable>new_owner</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER SERVER</command> changes the definition of a foreign
+   server.  The first form changes the server version string or the
+   generic options of the server (at least one clause is required).
+   The second form changes the owner of the server.
+  </para>
+
+  <para>
+   To alter the server you must be the owner of the server.
+   Additionally to alter the owner, you must own the server and also
+   be a direct or indirect member of the new owning role, and you must
+   have <literal>USAGE</> privilege on the server's foreign-data
+   wrapper.  (Note that superusers satisfy all these criteria
+   automatically.)
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">servername</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing server.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">serverversion</replaceable></term>
+    <listitem>
+     <para>
+      New server version.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
+    <listitem>
+     <para>
+      Change options for the
+      server.  <literal>ADD</>, <literal>SET</>, and <literal>DROP</>
+      specify the action to be performed.  <literal>ADD</> is assumed
+      if no operation is explicitly specified.  Option names must be
+      unique; names and values are also validated using the server's
+      foreign-data wrapper library.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Alter server <literal>foo</>, add connection options:
+<programlisting>
+ALTER SERVER foo OPTIONS (host 'foo', dbname 'foodb');
+</programlisting>
+  </para>
+
+  <para>
+   Alter server <literal>foo</>, change version,
+   change <literal>host</> option:
+<programlisting>
+ALTER SERVER foo VERSION '8.4' OPTIONS (SET host 'baz');
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>ALTER SERVER</command> conforms to ISO/IEC 9075-9 (SQL/MED).
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createserver" endterm="sql-createserver-title"></member>
+   <member><xref linkend="sql-dropserver" endterm="sql-dropserver-title"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/alter_user_mapping.sgml b/doc/src/sgml/ref/alter_user_mapping.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..38bff39ece7204dbbab11761e8acbb1a218630ea
--- /dev/null
+++ b/doc/src/sgml/ref/alter_user_mapping.sgml
@@ -0,0 +1,119 @@
+<!--
+$PostgreSQL: pgsql/doc/src/sgml/ref/alter_user_mapping.sgml,v 1.1 2008/12/19 16:25:16 petere Exp $
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-ALTERUSERMAPPING">
+ <refmeta>
+  <refentrytitle id="sql-alterusermapping-title">ALTER USER MAPPING</refentrytitle>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER USER MAPPING</refname>
+  <refpurpose>change the definition of a user mapping</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-alterusermapping">
+  <primary>ALTER USER MAPPING</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER USER MAPPING FOR { <replaceable class="parameter">username</replaceable> | USER | CURRENT_USER | PUBLIC }
+    SERVER <replaceable class="parameter">servername</replaceable>
+    OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER USER MAPPING</command> changes the definition of a
+   user mapping.  Only the owner of the server can change the user
+   mappings of that server.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">username</replaceable></term>
+    <listitem>
+     <para>
+      User name of the mapping. <literal>CURRENT_USER</>
+      and <literal>USER</> match the name of the current
+      user. <literal>PUBLIC</> is used to match all present and future
+      user names in the system.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">servername</replaceable></term>
+    <listitem>
+     <para>
+      Server name of the user mapping.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>OPTIONS ( [ ADD | SET | DROP ] <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
+    <listitem>
+     <para>
+      Change options for the user mapping. The new options override
+      any previously specified
+      options.  <literal>ADD</>, <literal>SET</>, and <literal>DROP</>
+      specify the action to be performed.  <literal>ADD</> is assumed
+      if no operation is explicitly specified.  Option names must be
+      unique; options are also validated by the server's foreign-data
+      wrapper.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Change the password for user mapping <literal>bob</>, server<literal> foo</>:
+<programlisting>
+ALTER USER MAPPING FOR bob SERVER foo OPTIONS (user 'bob', password 'public');
+</programlisting>
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>ALTER USER MAPPING</command> conforms to ISO/IEC 9075-9
+   (SQL/MED).  There is a subtle syntax issue: The standard omits
+   the <literal>FOR</literal> key word.  Since both <literal>CREATE
+   USER MAPPING</literal> and <literal>DROP USER MAPPING</literal> use
+   <literal>FOR</literal> in analogous positions, and IBM DB2 (being
+   the other major SQL/MED implementation) also requires it
+   for <literal>ALTER USER MAPPING</literal>, PostgreSQL diverges from
+   the standard here in the interest of consistency and
+   interoperability.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createusermapping" endterm="sql-createusermapping-title"></member>
+   <member><xref linkend="sql-dropusermapping" endterm="sql-dropusermapping-title"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/create_foreign_data_wrapper.sgml b/doc/src/sgml/ref/create_foreign_data_wrapper.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..d46e8e664065016fd69cdfdb95c70d0886bd1ab7
--- /dev/null
+++ b/doc/src/sgml/ref/create_foreign_data_wrapper.sgml
@@ -0,0 +1,185 @@
+<!--
+$PostgreSQL: pgsql/doc/src/sgml/ref/create_foreign_data_wrapper.sgml,v 1.1 2008/12/19 16:25:16 petere Exp $
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATEFOREIGNDATAWRAPPER">
+ <refmeta>
+  <refentrytitle id="sql-createforeigndatawrapper-title">CREATE FOREIGN DATA WRAPPER</refentrytitle>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE FOREIGN DATA WRAPPER</refname>
+  <refpurpose>define a new foreign-data wrapper</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-createforeigndatawrapper">
+  <primary>CREATE FOREIGN DATA WRAPPER</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable>
+    LIBRARY '<replaceable class="parameter">libraryname</replaceable>'
+    LANGUAGE C
+    [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE FOREIGN DATA WRAPPER</command> creates a new
+   foreign-data wrapper.  The user who defines a foreign-data wrapper
+   becomes its owner.
+  </para>
+
+  <para>
+   The foreign-data wrapper name must be unique within the database.
+  </para>
+
+  <para>
+   Only superusers can create foreign-data wrappers.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the foreign-data wrapper to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">libraryname</replaceable></term>
+    <listitem>
+     <para>
+      The name of the shared library implementing the foreign-data
+      wrapper.  The file name is specified in the same way as for
+      shared library names in <xref linkend="sql-createfunction"
+      endterm="sql-createfunction-title">; in particular, one can rely
+      on a search path and automatic addition of the system's standard
+      shared library file name extension.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>LANGUAGE C</literal></term>
+    <listitem>
+     <para>
+      Currently, only the C programming language is supported for
+      implementing foreign-data wrappers.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>OPTIONS ( <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
+    <listitem>
+     <para>
+      This clause specifies options for the new foreign-data wrapper.
+      The allowed option names and values are specific to each foreign
+      data wrapper and are validated using the foreign-data wrapper
+      library.  Option names must be unique.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   At the moment, the foreign-data wrapper functionality is very
+   rudimentary.  The purpose of foreign-data wrappers, foreign
+   servers, and user mappings is to store this information in a
+   standard way so that it can be queried by interested applications.
+   The functionality to actually query external data does not exist
+   yet.
+  </para>
+
+  <para>
+   The C language API for foreign-data wrappers is currently not
+   documented, stable, or complete.  Would-be authors of functionality
+   interfacing with the SQL/MED functionality are advised to contact
+   the PostgreSQL developers.
+  </para>
+
+  <para>
+   There are currently two foreign-data wrapper libraries
+   provided: <filename>dummy_fdw</filename>, which does nothing and
+   could be useful for testing,
+   and <filename>postgresql_fdw</filename>, which accepts options
+   corresponding to <application>libpq</> connection parameters.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Create a foreign-data wrapper <literal>dummy</> with
+   library <literal>dummy_fdw</>:
+<programlisting>
+CREATE FOREIGN DATA WRAPPER dummy LIBRARY 'dummy_fdw' LANGUAGE C;
+</programlisting>
+  </para>
+
+  <para>
+   Create a foreign-data wrapper <literal>postgresql</> with
+   library <literal>postgresql_fdw</>:
+<programlisting>
+CREATE FOREIGN DATA WRAPPER postgresql LIBRARY 'postgresql_fdw' LANGUAGE C;
+</programlisting>
+  </para>
+
+  <para>
+   Create a foreign-data wrapper <literal>mywrapper</> with library
+   <literal>/home/bob/mywrapper.so</> and some options:
+<programlisting>
+CREATE FOREIGN DATA WRAPPER mywrapper
+    LIBRARY '/home/bob/mywrapper.so'
+    LANGUAGE C
+    OPTIONS (debug 'true');
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>CREATE FOREIGN DATA WRAPPER</command> conforms to ISO/IEC
+   9075-9 (SQL/MED), with the exception that
+   the <literal>LIBRARY</literal> clause is not optional in
+   PostgreSQL.
+  </para>
+
+  <para>
+   Note, however, that the SQL/MED functionality as a whole is not yet
+   conforming.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-alterforeigndatawrapper" endterm="sql-alterforeigndatawrapper-title"></member>
+   <member><xref linkend="sql-dropforeigndatawrapper" endterm="sql-dropforeigndatawrapper-title"></member>
+   <member><xref linkend="sql-createserver" endterm="sql-createserver-title"></member>
+   <member><xref linkend="sql-createusermapping" endterm="sql-createusermapping-title"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/create_server.sgml b/doc/src/sgml/ref/create_server.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..894eecc55639215620bf69f5814e096dec1064cb
--- /dev/null
+++ b/doc/src/sgml/ref/create_server.sgml
@@ -0,0 +1,140 @@
+<!--
+$PostgreSQL: pgsql/doc/src/sgml/ref/create_server.sgml,v 1.1 2008/12/19 16:25:16 petere Exp $
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATESERVER">
+ <refmeta>
+  <refentrytitle id="sql-createserver-title">CREATE SERVER</refentrytitle>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE SERVER</refname>
+  <refpurpose>define a new foreign server</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-createserver">
+  <primary>CREATE SERVER</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE SERVER <replaceable class="parameter">servername</replaceable> [ TYPE 'servertype' ] [ VERSION 'serverversion' ]
+    FOREIGN DATA WRAPPER <replaceable class="parameter">fdwname</replaceable>
+    [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE SERVER</command> defines a new foreign server.  The
+   user who defines the server becomes its owner.
+  </para>
+
+  <para>
+   The server name must be unique within database.
+  </para>
+
+  <para>
+   Creating a server requires <literal>USAGE</> privilege on the
+   foreign-data wrapper being used.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">servername</replaceable></term>
+    <listitem>
+     <para>
+      The name of the foreign server to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">servertype</replaceable></term>
+    <listitem>
+     <para>
+      Optional server type.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">serverversion</replaceable></term>
+    <listitem>
+     <para>
+      Optional server version.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">fdwname</replaceable></term>
+    <listitem>
+     <para>
+      The name of the foreign-data wrapper that manages the server.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>OPTIONS ( <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
+    <listitem>
+     <para>
+      This clause specifies the options for the server.  The options
+      typically define the connection details of the server, but the
+      actual names and values are dependent on the server's
+      foreign-data wrapper.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Create a server <literal>foo</> that uses the built-in foreign-data
+   wrapper <literal>default</>:
+<programlisting>
+CREATE SERVER foo FOREIGN DATA WRAPPER "default";
+</programlisting>
+  </para>
+
+  <para>
+   Create a server <literal>myserver</> that uses the
+   foreign-data wrapper <literal>pgsql</>:
+<programlisting>
+CREATE SERVER myserver FOREIGN DATA WRAPPER pgsql OPTIONS (host 'foo', dbname 'foodb', port '5432');
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>CREATE SERVER</command> conforms to ISO/IEC 9075-9 (SQL/MED).
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-alterserver" endterm="sql-alterserver-title"></member>
+   <member><xref linkend="sql-dropserver" endterm="sql-dropserver-title"></member>
+   <member><xref linkend="sql-createforeigndatawrapper" endterm="sql-createforeigndatawrapper-title"></member>
+   <member><xref linkend="sql-createusermapping" endterm="sql-createusermapping-title"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/create_user_mapping.sgml b/doc/src/sgml/ref/create_user_mapping.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..07cfb0a4c209693ec607873668cbd6c207c244b9
--- /dev/null
+++ b/doc/src/sgml/ref/create_user_mapping.sgml
@@ -0,0 +1,111 @@
+<!--
+$PostgreSQL: pgsql/doc/src/sgml/ref/create_user_mapping.sgml,v 1.1 2008/12/19 16:25:16 petere Exp $
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATEUSERMAPPING">
+ <refmeta>
+  <refentrytitle id="sql-createusermapping-title">CREATE USER MAPPING</refentrytitle>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE USER MAPPING</refname>
+  <refpurpose>define a new mapping of a user to a foreign server</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-createusermapping">
+  <primary>CREATE USER MAPPING</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE USER MAPPING FOR { <replaceable class="parameter">username</replaceable> | USER | CURRENT_USER | PUBLIC }
+    SERVER <replaceable class="parameter">servername</replaceable>
+    [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [ , ... ] ) ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE USER MAPPING</command> defines a mapping of a user
+   to a foreign server.  You must be the owner of the server to define
+   user mappings for it.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">username</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing user that is mapped to foreign server.
+      <literal>CURRENT_USER</> and <literal>USER</> match the name of
+      the current user.  <literal>PUBLIC</> is used to match all
+      present and future user names in the system.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">servername</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing server for which the user mapping is
+      to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>OPTIONS ( <replaceable class="PARAMETER">option</replaceable> ['<replaceable class="PARAMETER">value</replaceable>'] [, ... ] )</literal></term>
+    <listitem>
+     <para>
+      This clause specifies the options of the user mapping.  The
+      options typically define the actual user name and password of
+      the mapping.  Option names must be unque.  The allowed option
+      names and values are specific to the server's foreign-data wrapper.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Create a user mapping for user <literal>bob</>, server <literal>foo</>:
+<programlisting>
+CREATE USER MAPPING FOR bob SERVER foo OPTIONS (user 'bob', password 'secret');
+</programlisting>
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>CREATE USER MAPPING</command> conforms to ISO/IEC 9075-9 (SQL/MED).
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-alterusermapping" endterm="sql-alterusermapping-title"></member>
+   <member><xref linkend="sql-dropusermapping" endterm="sql-dropusermapping-title"></member>
+   <member><xref linkend="sql-createforeigndatawrapper" endterm="sql-createforeigndatawrapper-title"></member>
+   <member><xref linkend="sql-createserver" endterm="sql-createserver-title"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_foreign_data_wrapper.sgml b/doc/src/sgml/ref/drop_foreign_data_wrapper.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..e28b790ceafca11f6aa563a8d411c651fc643e13
--- /dev/null
+++ b/doc/src/sgml/ref/drop_foreign_data_wrapper.sgml
@@ -0,0 +1,112 @@
+<!--
+$PostgreSQL: pgsql/doc/src/sgml/ref/drop_foreign_data_wrapper.sgml,v 1.1 2008/12/19 16:25:16 petere Exp $
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPFOREIGNDATAWRAPPER">
+ <refmeta>
+  <refentrytitle id="sql-dropforeigndatawrapper-title">DROP FOREIGN DATA WRAPPER</refentrytitle>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP FOREIGN DATA WRAPPER</refname>
+  <refpurpose>remove a foreign-data wrapper</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-dropforeigndatawrapper">
+  <primary>DROP FOREIGN DATA WRAPPER</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP FOREIGN DATA WRAPPER [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP FOREIGN DATA WRAPPER</command> removes an existing
+   foreign-data wrapper.  To execute this command, the current user
+   must be the owner of the foreign-data wrapper.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the foreign-data wrapper does not
+      exist.  A notice is issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing foreign-data wrapper.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically drop objects that depend on the foreign-data
+      wrapper (such as servers).
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to drop the foreign-data wrappers if any objects depend
+      on it.  This is the default.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Drop the foreign-data wrapper <literal>dbi</>:
+<programlisting>
+DROP FOREIGN DATA WRAPPER dbi;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>DROP FOREIGN DATA WRAPPER</command> conforms to ISO/IEC
+   9075-9 (SQL/MED).  The <literal>IF EXISTS</> clause is
+   a <productname>PostgreSQL</> extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createforeigndatawrapper" endterm="sql-createforeigndatawrapper-title"></member>
+   <member><xref linkend="sql-alterforeigndatawrapper" endterm="sql-alterforeigndatawrapper-title"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_server.sgml b/doc/src/sgml/ref/drop_server.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..b9ddac51b577c836f665f0acf697db630438b68d
--- /dev/null
+++ b/doc/src/sgml/ref/drop_server.sgml
@@ -0,0 +1,112 @@
+<!--
+$PostgreSQL: pgsql/doc/src/sgml/ref/drop_server.sgml,v 1.1 2008/12/19 16:25:16 petere Exp $
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPSERVER">
+ <refmeta>
+  <refentrytitle id="sql-dropserver-title">DROP SERVER</refentrytitle>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP SERVER</refname>
+  <refpurpose>remove a foreign server descriptor</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-dropserver">
+  <primary>DROP SERVER</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP SERVER [ IF EXISTS ] <replaceable class="parameter">servername</replaceable> [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP SERVER</command> removes an existing foreign server
+   descriptor.  To execute this command, the current user must be the
+   owner of the server.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the server does not exist.  A notice is
+      issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">servername</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing server.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically drop objects that depend on the server (such as
+      user mappings).
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to drop the server if any objects depend on it.  This is
+      the default.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Drop a server <literal>foo</> if it exists:
+<programlisting>
+DROP SERVER IF EXISTS foo;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>DROP SERVER</command> conforms to ISO/IEC 9075-9
+   (SQL/MED).  The <literal>IF EXISTS</> clause is
+   a <productname>PostgreSQL</> extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createserver" endterm="sql-createserver-title"></member>
+   <member><xref linkend="sql-alterserver" endterm="sql-alterserver-title"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_user_mapping.sgml b/doc/src/sgml/ref/drop_user_mapping.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..c22dedb661ad5227a6cf3ac0fce85173052cdb2b
--- /dev/null
+++ b/doc/src/sgml/ref/drop_user_mapping.sgml
@@ -0,0 +1,104 @@
+<!--
+$PostgreSQL: pgsql/doc/src/sgml/ref/drop_user_mapping.sgml,v 1.1 2008/12/19 16:25:16 petere Exp $
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPUSERMAPPING">
+ <refmeta>
+  <refentrytitle id="sql-dropusermapping-title">DROP USER MAPPING</refentrytitle>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP USER MAPPING</refname>
+  <refpurpose>remove a user mapping for a foreign server</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-dropusermapping">
+  <primary>DROP USER MAPPING</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP USER MAPPING [ IF EXISTS ] FOR { <replaceable class="parameter">username</replaceable> | USER | CURRENT_USER | PUBLIC } SERVER <replaceable class="parameter">servername</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP USER MAPPING</command> removes an existing user
+   mapping from foreign server.  To execute this command, the current
+   user must be the owner of the server containing the mapping.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the user mapping does not exist.  A
+      notice is issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">username</replaceable></term>
+    <listitem>
+     <para>
+      User name of the mapping.  <literal>CURRENT_USER</>
+      and <literal>USER</> match the name of the current
+      user.  <literal>PUBLIC</> is used to match all present and
+      future user names in the system.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">servername</replaceable></term>
+    <listitem>
+     <para>
+      Server name of the user mapping.
+     </para>
+    </listitem>
+   </varlistentry>
+    </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Drop a user mapping <literal>bob</>, server <literal>foo</> if it exists:
+<programlisting>
+DROP USER MAPPING IF EXISTS FOR bob SERVER foo;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>DROP USER MAPPING</command> conforms to ISO/IEC 9075-9
+   (SQL/MED).  The <literal>IF EXISTS</> clause is
+   a <productname>PostgreSQL</> extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createusermapping" endterm="sql-createusermapping-title"></member>
+   <member><xref linkend="sql-alterusermapping" endterm="sql-alterusermapping-title"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index 5c907e1ef0518ebb20ebc88f46a50ec8cacd1dbb..dafb8ffb523885fda8d99b1804a72d4380a8a10f 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/grant.sgml,v 1.72 2008/11/14 10:22:47 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/grant.sgml,v 1.73 2008/12/19 16:25:16 petere Exp $
 PostgreSQL documentation
 -->
 
@@ -35,6 +35,14 @@ GRANT { { CREATE | CONNECT | TEMPORARY | TEMP } [,...] | ALL [ PRIVILEGES ] }
     ON DATABASE <replaceable>dbname</replaceable> [, ...]
     TO { [ GROUP ] <replaceable class="PARAMETER">rolename</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
 
+GRANT { USAGE | ALL [ PRIVILEGES ] }
+    ON FOREIGN DATA WRAPPER <replaceable>fdwname</> [, ...]
+    TO { [ GROUP ] <replaceable class="PARAMETER">rolename</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
+
+GRANT { USAGE | ALL [ PRIVILEGES ] }
+    ON FOREIGN SERVER <replaceable>servername</> [, ...]
+    TO { [ GROUP ] <replaceable class="PARAMETER">rolename</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
+
 GRANT { EXECUTE | ALL [ PRIVILEGES ] }
     ON FUNCTION <replaceable>funcname</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) [, ...]
     TO { [ GROUP ] <replaceable class="PARAMETER">rolename</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
@@ -61,10 +69,10 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
   <para>
    The <command>GRANT</command> command has two basic variants: one
    that grants privileges on a database object (table, view, sequence,
-   database, function, procedural language, schema, or tablespace),
-   and one that grants membership in a role.  These variants are
-   similar in many ways, but they are different enough to be described
-   separately.
+   database, foreign-data wrapper, foreign server, function,
+   procedural language, schema, or tablespace), and one that grants
+   membership in a role.  These variants are similar in many ways, but
+   they are different enough to be described separately.
   </para>
 
   <para>
@@ -299,6 +307,14 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
        For sequences, this privilege allows the use of the
        <function>currval</function> and <function>nextval</function> functions.
       </para>
+      <para>
+       For foreign-data wrappers, this privilege enables the grantee
+       to create new servers using that foreign-data wrapper.
+      </para>
+      <para>
+       For servers, this privilege enables the grantee to query the
+       options of the server and associated user mappings.
+      </para>
      </listitem>
     </varlistentry>
 
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index fa7f68caf40ecaeb5d47acb361535eb7159e9c22..fb394b16eefe4bf3d6d5785f739948d75921dd4a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.213 2008/12/01 09:20:37 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.214 2008/12/19 16:25:16 petere Exp $
 PostgreSQL documentation
 -->
 
@@ -946,6 +946,64 @@ testdb=&gt;
       </varlistentry>
 
 
+      <varlistentry>
+        <term><literal>\des [ <replaceable class="parameter">pattern</replaceable> ]</literal></term>
+        <term><literal>\des+ [ <replaceable class="parameter">pattern</replaceable> ]</literal></term>
+        <listitem>
+        <para>
+        Lists all foreign servers (mnemonic: <quote>external
+        servers</quote>).
+        If <replaceable class="parameter">pattern</replaceable> is
+        specified, only those servers whose name matches the pattern
+        are listed.  If the form <literal>\des+</literal> is used, a
+        full desription of each server is shown, including the
+        server's ACL, type, version, and options.
+        </para>
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry>
+        <term><literal>\deu [ <replaceable class="parameter">pattern</replaceable> ]</literal></term>
+        <term><literal>\deu+ [ <replaceable class="parameter">pattern</replaceable> ]</literal></term>
+        <listitem>
+        <para>
+        Lists all user mappings (mnemonic: <quote>external
+        users</quote>).
+        If <replaceable class="parameter">pattern</replaceable> is
+        specified, only those mappings whose user names match the
+        pattern are listed.  If the form <literal>\deu+</literal> is
+        used, additional information about each mapping is shown.
+        </para>
+
+        <caution>
+        <para>
+        <literal>\deu+</literal> might also display the user name and
+        password of the remote user, so care should be taken not to
+        disclose them.
+        </para>
+        </caution>
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry>
+        <term><literal>\dew [ <replaceable class="parameter">pattern</replaceable> ]</literal></term>
+        <term><literal>\dew+ [ <replaceable class="parameter">pattern</replaceable> ]</literal></term>
+        <listitem>
+        <para>
+        Lists all foreign-data wrappers (mnemonic: <quote>external
+        wrappers</quote>).
+        If <replaceable class="parameter">pattern</replaceable> is
+        specified, only those foreign-data wrappers whose name matches
+        the pattern are listed.  If the form <literal>\dew+</literal>
+        is used, the ACL and options of the foreign-data wrapper are
+        also shown.
+        </para>
+        </listitem>
+      </varlistentry>
+
+
       <varlistentry>
         <term><literal>\df [ <replaceable class="parameter">pattern</replaceable> ]</literal></term>
         <term><literal>\df+ [ <replaceable class="parameter">pattern</replaceable> ]</literal></term>
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 11f576e0fb7746837e2d04e7f373ad7886ea6de9..c8e91e0a159aa2a14d94508418ea418032be7dd9 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/revoke.sgml,v 1.49 2008/11/14 10:22:47 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/revoke.sgml,v 1.50 2008/12/19 16:25:16 petere Exp $
 PostgreSQL documentation
 -->
 
@@ -41,6 +41,18 @@ REVOKE [ GRANT OPTION FOR ]
     FROM { [ GROUP ] <replaceable class="PARAMETER">rolename</replaceable> | PUBLIC } [, ...]
     [ CASCADE | RESTRICT ]
 
+REVOKE [ GRANT OPTION FOR ]
+    { USAGE | ALL [ PRIVILEGES ] }
+    ON FOREIGN DATA WRAPPER <replaceable>fdwname</replaceable> [, ...]
+    FROM { [ GROUP ] <replaceable class="PARAMETER">rolename</replaceable> | PUBLIC } [, ...]
+    [ CASCADE | RESTRICT ]
+
+REVOKE [ GRANT OPTION FOR ]
+    { USAGE | ALL [ PRIVILEGES ] }
+    ON FOREIGN SERVER <replaceable>servername</replaceable> [, ...]
+    FROM { [ GROUP ] <replaceable class="PARAMETER">rolename</replaceable> | PUBLIC } [, ...]
+    [ CASCADE | RESTRICT ]
+
 REVOKE [ GRANT OPTION FOR ]
     { EXECUTE | ALL [ PRIVILEGES ] }
     ON FUNCTION <replaceable>funcname</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) [, ...]
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index a0aed8edf535689d54f3a1ac4eca16b8fddc4bfd..d3a862959d900c224ecd0207fcc86f652cfe1974 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/reference.sgml,v 1.66 2008/03/27 17:24:16 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/reference.sgml,v 1.67 2008/12/19 16:25:16 petere Exp $ -->
 
 <part id="reference">
  <title>Reference</title>
@@ -38,6 +38,7 @@
    &alterConversion;
    &alterDatabase;
    &alterDomain;
+   &alterForeignDataWrapper;
    &alterFunction;
    &alterGroup;
    &alterIndex;
@@ -48,6 +49,7 @@
    &alterRole;
    &alterSchema;
    &alterSequence;
+   &alterServer;
    &alterTable;
    &alterTableSpace;
    &alterTSConfig;
@@ -57,6 +59,7 @@
    &alterTrigger;
    &alterType;
    &alterUser;
+   &alterUserMapping;
    &alterView;
    &analyze;
    &begin;
@@ -73,6 +76,7 @@
    &createConversion;
    &createDatabase;
    &createDomain;
+   &createForeignDataWrapper;
    &createFunction;
    &createGroup;
    &createIndex;
@@ -84,6 +88,7 @@
    &createRule;
    &createSchema;
    &createSequence;
+   &createServer;
    &createTable;
    &createTableAs;
    &createTableSpace;
@@ -94,6 +99,7 @@
    &createTrigger;
    &createType;
    &createUser;
+   &createUserMapping;
    &createView;
    &deallocate;
    &declare;
@@ -104,6 +110,7 @@
    &dropConversion;
    &dropDatabase;
    &dropDomain;
+   &dropForeignDataWrapper;
    &dropFunction;
    &dropGroup;
    &dropIndex;
@@ -116,6 +123,7 @@
    &dropRule;
    &dropSchema;
    &dropSequence;
+   &dropServer;
    &dropTable;
    &dropTableSpace;
    &dropTSConfig;
@@ -125,6 +133,7 @@
    &dropTrigger;
    &dropType;
    &dropUser;
+   &dropUserMapping;
    &dropView;
    &end;
    &execute;
diff --git a/src/Makefile b/src/Makefile
index 61950967fde651165d9bcbb986448722e99d1ca7..13cbeb973456f9aa94e3c4435fd89bbfcc11c15c 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -4,7 +4,7 @@
 #
 # Copyright (c) 1994, Regents of the University of California
 #
-# $PostgreSQL: pgsql/src/Makefile,v 1.43 2008/03/18 16:24:50 petere Exp $
+# $PostgreSQL: pgsql/src/Makefile,v 1.44 2008/12/19 16:25:16 petere Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -19,6 +19,7 @@ all install installdirs uninstall distprep:
 	$(MAKE) -C backend $@
 	$(MAKE) -C backend/utils/mb/conversion_procs $@
 	$(MAKE) -C backend/snowball $@
+	$(MAKE) -C backend/foreign $@-fdw
 	$(MAKE) -C include $@
 	$(MAKE) -C interfaces $@
 	$(MAKE) -C bin $@
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 7fd613f6c9fc5d6b93efd2ed0daae88a9b910bf7..1903e554cf952580b5ceb16a13dbe1f8ba25336f 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -5,7 +5,7 @@
 # Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
 # Portions Copyright (c) 1994, Regents of the University of California
 #
-# $PostgreSQL: pgsql/src/backend/Makefile,v 1.130 2008/08/29 13:02:32 petere Exp $
+# $PostgreSQL: pgsql/src/backend/Makefile,v 1.131 2008/12/19 16:25:16 petere Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -14,7 +14,7 @@ subdir = src/backend
 top_builddir = ../..
 include $(top_builddir)/src/Makefile.global
 
-SUBDIRS = access bootstrap catalog parser commands executor lib libpq \
+SUBDIRS = access bootstrap catalog parser commands executor foreign lib libpq \
 	main nodes optimizer port postmaster regex rewrite \
 	storage tcop tsearch utils $(top_builddir)/src/timezone
 
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index bb4c42135c554f2b0fc447ee142ac3e27523e0d9..58973c9a88991588372d59b42e8ddae702447d84 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -2,7 +2,7 @@
 #
 # Makefile for backend/catalog
 #
-# $PostgreSQL: pgsql/src/backend/catalog/Makefile,v 1.67 2008/11/19 10:34:51 heikki Exp $
+# $PostgreSQL: pgsql/src/backend/catalog/Makefile,v 1.68 2008/12/19 16:25:16 petere Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -36,6 +36,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
 	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h \
+	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index f25c7608e41e4ee3d5f9e9207b1d44bf52f62570..cecfe84e7a3c41f03ac56e7c05976fee07cf15ca 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.149 2008/11/02 01:45:27 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.150 2008/12/19 16:25:16 petere Exp $
  *
  * NOTES
  *	  See acl.h.
@@ -27,6 +27,8 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_foreign_data_wrapper.h"
+#include "catalog/pg_foreign_server.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
@@ -38,6 +40,7 @@
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_dict.h"
 #include "commands/dbcommands.h"
+#include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
 #include "utils/acl.h"
@@ -50,6 +53,8 @@
 
 static void ExecGrant_Relation(InternalGrant *grantStmt);
 static void ExecGrant_Database(InternalGrant *grantStmt);
+static void ExecGrant_Fdw(InternalGrant *grantStmt);
+static void ExecGrant_ForeignServer(InternalGrant *grantStmt);
 static void ExecGrant_Function(InternalGrant *grantStmt);
 static void ExecGrant_Language(InternalGrant *grantStmt);
 static void ExecGrant_Namespace(InternalGrant *grantStmt);
@@ -188,6 +193,12 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
 		case ACL_KIND_TABLESPACE:
 			whole_mask = ACL_ALL_RIGHTS_TABLESPACE;
 			break;
+		case ACL_KIND_FDW:
+			whole_mask = ACL_ALL_RIGHTS_FDW;
+			break;
+		case ACL_KIND_FOREIGN_SERVER:
+			whole_mask = ACL_ALL_RIGHTS_FOREIGN_SERVER;
+			break;
 		default:
 			elog(ERROR, "unrecognized object kind: %d", objkind);
 			/* not reached, but keep compiler quiet */
@@ -323,6 +334,14 @@ ExecuteGrantStmt(GrantStmt *stmt)
 			all_privileges = ACL_ALL_RIGHTS_TABLESPACE;
 			errormsg = gettext_noop("invalid privilege type %s for tablespace");
 			break;
+		case ACL_OBJECT_FDW:
+			all_privileges = ACL_ALL_RIGHTS_FDW;
+			errormsg = gettext_noop("invalid privilege type %s for foreign-data wrapper");
+			break;
+		case ACL_OBJECT_FOREIGN_SERVER:
+			all_privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER;
+			errormsg = gettext_noop("invalid privilege type %s for foreign server");
+			break;
 		default:
 			/* keep compiler quiet */
 			all_privileges = ACL_NO_RIGHTS;
@@ -380,6 +399,12 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 		case ACL_OBJECT_DATABASE:
 			ExecGrant_Database(istmt);
 			break;
+		case ACL_OBJECT_FDW:
+			ExecGrant_Fdw(istmt);
+			break;
+		case ACL_OBJECT_FOREIGN_SERVER:
+			ExecGrant_ForeignServer(istmt);
+			break;
 		case ACL_OBJECT_FUNCTION:
 			ExecGrant_Function(istmt);
 			break;
@@ -520,6 +545,24 @@ objectNamesToOids(GrantObjectType objtype, List *objnames)
 				heap_close(relation, AccessShareLock);
 			}
 			break;
+		case ACL_OBJECT_FDW:
+			foreach(cell, objnames)
+			{
+				char   *fdwname = strVal(lfirst(cell));
+				Oid		fdwid = GetForeignDataWrapperOidByName(fdwname, false);
+
+				objects = lappend_oid(objects, fdwid);
+			}
+			break;
+		case ACL_OBJECT_FOREIGN_SERVER:
+			foreach(cell, objnames)
+			{
+				char   *srvname = strVal(lfirst(cell));
+				Oid		srvid = GetForeignServerOidByName(srvname, false);
+
+				objects = lappend_oid(objects, srvid);
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) objtype);
@@ -840,6 +883,239 @@ ExecGrant_Database(InternalGrant *istmt)
 	heap_close(relation, RowExclusiveLock);
 }
 
+static void
+ExecGrant_Fdw(InternalGrant *istmt)
+{
+	Relation	relation;
+	ListCell   *cell;
+
+	if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
+		istmt->privileges = ACL_ALL_RIGHTS_FDW;
+
+	relation = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+	foreach(cell, istmt->objects)
+	{
+		Oid			fdwid = lfirst_oid(cell);
+		Form_pg_foreign_data_wrapper pg_fdw_tuple;
+		Datum		aclDatum;
+		bool		isNull;
+		AclMode		avail_goptions;
+		AclMode		this_privileges;
+		Acl		   *old_acl;
+		Acl		   *new_acl;
+		Oid			grantorId;
+		Oid			ownerId;
+		HeapTuple	tuple;
+		HeapTuple	newtuple;
+		Datum		values[Natts_pg_foreign_data_wrapper];
+		bool		nulls[Natts_pg_foreign_data_wrapper];
+		bool		replaces[Natts_pg_foreign_data_wrapper];
+		int			noldmembers;
+		int			nnewmembers;
+		Oid		   *oldmembers;
+		Oid		   *newmembers;
+
+		tuple = SearchSysCache(FOREIGNDATAWRAPPEROID,
+							   ObjectIdGetDatum(fdwid),
+							   0, 0, 0);
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+
+		pg_fdw_tuple = (Form_pg_foreign_data_wrapper) GETSTRUCT(tuple);
+
+		/*
+		 * Get owner ID and working copy of existing ACL. If there's no ACL,
+		 * substitute the proper default.
+		 */
+		ownerId = pg_fdw_tuple->fdwowner;
+		aclDatum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID, tuple,
+								   Anum_pg_foreign_data_wrapper_fdwacl,
+								   &isNull);
+		if (isNull)
+			old_acl = acldefault(ACL_OBJECT_FDW, ownerId);
+		else
+			old_acl = DatumGetAclPCopy(aclDatum);
+
+		/* Determine ID to do the grant as, and available grant options */
+		select_best_grantor(GetUserId(), istmt->privileges,
+							old_acl, ownerId,
+							&grantorId, &avail_goptions);
+
+		/*
+		 * Restrict the privileges to what we can actually grant, and emit the
+		 * standards-mandated warning and error messages.
+		 */
+		this_privileges =
+			restrict_and_check_grant(istmt->is_grant, avail_goptions,
+									 istmt->all_privs, istmt->privileges,
+									 fdwid, grantorId, ACL_KIND_FDW,
+									 NameStr(pg_fdw_tuple->fdwname));
+
+		/*
+		 * Generate new ACL.
+		 *
+		 * We need the members of both old and new ACLs so we can correct the
+		 * shared dependency information.
+		 */
+		noldmembers = aclmembers(old_acl, &oldmembers);
+
+		new_acl = merge_acl_with_grant(old_acl, istmt->is_grant,
+									   istmt->grant_option, istmt->behavior,
+									   istmt->grantees, this_privileges,
+									   grantorId, ownerId);
+
+		nnewmembers = aclmembers(new_acl, &newmembers);
+
+		/* finished building new ACL value, now insert it */
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, false, sizeof(nulls));
+		MemSet(replaces, false, sizeof(replaces));
+
+		replaces[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true;
+		values[Anum_pg_foreign_data_wrapper_fdwacl - 1] = PointerGetDatum(new_acl);
+
+		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
+									 nulls, replaces);
+
+		simple_heap_update(relation, &newtuple->t_self, newtuple);
+
+		/* keep the catalog indexes up to date */
+		CatalogUpdateIndexes(relation, newtuple);
+
+		/* Update the shared dependency ACL info */
+		updateAclDependencies(ForeignDataWrapperRelationId, HeapTupleGetOid(tuple),
+							  ownerId, istmt->is_grant,
+							  noldmembers, oldmembers,
+							  nnewmembers, newmembers);
+
+		ReleaseSysCache(tuple);
+
+		pfree(new_acl);
+
+		/* prevent error when processing duplicate objects */
+		CommandCounterIncrement();
+	}
+
+	heap_close(relation, RowExclusiveLock);
+}
+
+static void ExecGrant_ForeignServer(InternalGrant *istmt)
+{
+	Relation	relation;
+	ListCell   *cell;
+
+	if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
+		istmt->privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER;
+
+	relation = heap_open(ForeignServerRelationId, RowExclusiveLock);
+
+	foreach(cell, istmt->objects)
+	{
+		Oid			srvid = lfirst_oid(cell);
+		Form_pg_foreign_server pg_server_tuple;
+		Datum		aclDatum;
+		bool		isNull;
+		AclMode		avail_goptions;
+		AclMode		this_privileges;
+		Acl		   *old_acl;
+		Acl		   *new_acl;
+		Oid			grantorId;
+		Oid			ownerId;
+		HeapTuple	tuple;
+		HeapTuple	newtuple;
+		Datum		values[Natts_pg_foreign_server];
+		bool		nulls[Natts_pg_foreign_server];
+		bool		replaces[Natts_pg_foreign_server];
+		int			noldmembers;
+		int			nnewmembers;
+		Oid		   *oldmembers;
+		Oid		   *newmembers;
+
+		tuple = SearchSysCache(FOREIGNSERVEROID,
+							   ObjectIdGetDatum(srvid),
+							   0, 0, 0);
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for foreign server %u", srvid);
+
+		pg_server_tuple = (Form_pg_foreign_server) GETSTRUCT(tuple);
+
+		/*
+		 * Get owner ID and working copy of existing ACL. If there's no ACL,
+		 * substitute the proper default.
+		 */
+		ownerId = pg_server_tuple->srvowner;
+		aclDatum = SysCacheGetAttr(FOREIGNSERVEROID, tuple,
+								   Anum_pg_foreign_server_srvacl,
+								   &isNull);
+		if (isNull)
+			old_acl = acldefault(ACL_OBJECT_FOREIGN_SERVER, ownerId);
+		else
+			old_acl = DatumGetAclPCopy(aclDatum);
+
+		/* Determine ID to do the grant as, and available grant options */
+		select_best_grantor(GetUserId(), istmt->privileges,
+							old_acl, ownerId,
+							&grantorId, &avail_goptions);
+
+		/*
+		 * Restrict the privileges to what we can actually grant, and emit the
+		 * standards-mandated warning and error messages.
+		 */
+		this_privileges =
+			restrict_and_check_grant(istmt->is_grant, avail_goptions,
+									 istmt->all_privs, istmt->privileges,
+									 srvid, grantorId, ACL_KIND_FOREIGN_SERVER,
+									 NameStr(pg_server_tuple->srvname));
+
+		/*
+		 * Generate new ACL.
+		 *
+		 * We need the members of both old and new ACLs so we can correct the
+		 * shared dependency information.
+		 */
+		noldmembers = aclmembers(old_acl, &oldmembers);
+
+		new_acl = merge_acl_with_grant(old_acl, istmt->is_grant,
+									   istmt->grant_option, istmt->behavior,
+									   istmt->grantees, this_privileges,
+									   grantorId, ownerId);
+
+		nnewmembers = aclmembers(new_acl, &newmembers);
+
+		/* finished building new ACL value, now insert it */
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, false, sizeof(nulls));
+		MemSet(replaces, false, sizeof(replaces));
+
+		replaces[Anum_pg_foreign_server_srvacl - 1] = true;
+		values[Anum_pg_foreign_server_srvacl - 1] = PointerGetDatum(new_acl);
+
+		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
+									 nulls, replaces);
+
+		simple_heap_update(relation, &newtuple->t_self, newtuple);
+
+		/* keep the catalog indexes up to date */
+		CatalogUpdateIndexes(relation, newtuple);
+
+		/* Update the shared dependency ACL info */
+		updateAclDependencies(ForeignServerRelationId, HeapTupleGetOid(tuple),
+							  ownerId, istmt->is_grant,
+							  noldmembers, oldmembers,
+							  nnewmembers, newmembers);
+
+		ReleaseSysCache(tuple);
+
+		pfree(new_acl);
+
+		/* prevent error when processing duplicate objects */
+		CommandCounterIncrement();
+	}
+
+	heap_close(relation, RowExclusiveLock);
+}
+
 static void
 ExecGrant_Function(InternalGrant *istmt)
 {
@@ -1428,7 +1704,11 @@ static const char *const no_priv_msg[MAX_ACL_KIND] =
 	/* ACL_KIND_TSDICTIONARY */
 	gettext_noop("permission denied for text search dictionary %s"),
 	/* ACL_KIND_TSCONFIGURATION */
-	gettext_noop("permission denied for text search configuration %s")
+	gettext_noop("permission denied for text search configuration %s"),
+	/* ACL_KIND_FDW */
+	gettext_noop("permission denied for foreign-data wrapper %s"),
+	/* ACL_KIND_FOREIGN_SERVER */
+	gettext_noop("permission denied for foreign server %s")
 };
 
 static const char *const not_owner_msg[MAX_ACL_KIND] =
@@ -1460,7 +1740,11 @@ static const char *const not_owner_msg[MAX_ACL_KIND] =
 	/* ACL_KIND_TSDICTIONARY */
 	gettext_noop("must be owner of text search dictionary %s"),
 	/* ACL_KIND_TSCONFIGURATION */
-	gettext_noop("must be owner of text search configuration %s")
+	gettext_noop("must be owner of text search configuration %s"),
+	/* ACL_KIND_FDW */
+	gettext_noop("must be owner of foreign-data wrapper %s"),
+	/* ACL_KIND_FOREIGN_SERVER */
+	gettext_noop("must be owner of foreign server %s")
 };
 
 
@@ -1534,6 +1818,10 @@ pg_aclmask(AclObjectKind objkind, Oid table_oid, Oid roleid,
 			return pg_namespace_aclmask(table_oid, roleid, mask, how);
 		case ACL_KIND_TABLESPACE:
 			return pg_tablespace_aclmask(table_oid, roleid, mask, how);
+		case ACL_KIND_FDW:
+			return pg_foreign_data_wrapper_aclmask(table_oid, roleid, mask, how);
+		case ACL_KIND_FOREIGN_SERVER:
+			return pg_foreign_server_aclmask(table_oid, roleid, mask, how);
 		default:
 			elog(ERROR, "unrecognized objkind: %d",
 				 (int) objkind);
@@ -1962,6 +2250,131 @@ pg_tablespace_aclmask(Oid spc_oid, Oid roleid,
 	return result;
 }
 
+/*
+ * Exported routine for examining a user's privileges for a foreign
+ * data wrapper
+ */
+AclMode
+pg_foreign_data_wrapper_aclmask(Oid fdw_oid, Oid roleid,
+								AclMode mask, AclMaskHow how)
+{
+	AclMode		result;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+	Acl		   *acl;
+	Oid			ownerId;
+
+	Form_pg_foreign_data_wrapper fdwForm;
+
+	/* Bypass permission checks for superusers */
+	if (superuser_arg(roleid))
+		return mask;
+
+	/*
+	 * Must get the FDW's tuple from pg_foreign_data_wrapper
+	 */
+	tuple = SearchSysCache(FOREIGNDATAWRAPPEROID,
+						   ObjectIdGetDatum(fdw_oid),
+						   0, 0, 0);
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				 (errmsg("foreign-data wrapper with OID %u does not exist",
+						fdw_oid)));
+	fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(tuple);
+
+	/*
+	 * Normal case: get the FDW's ACL from pg_foreign_data_wrapper
+	 */
+	ownerId = fdwForm->fdwowner;
+
+	aclDatum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID, tuple,
+							   Anum_pg_foreign_data_wrapper_fdwacl, &isNull);
+	if (isNull)
+	{
+		/* No ACL, so build default ACL */
+		acl = acldefault(ACL_OBJECT_FDW, ownerId);
+		aclDatum = (Datum) 0;
+	}
+	else
+	{
+		/* detoast rel's ACL if necessary */
+		acl = DatumGetAclP(aclDatum);
+	}
+
+	result = aclmask(acl, roleid, ownerId, mask, how);
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+/*
+ * Exported routine for examining a user's privileges for a foreign
+ * server.
+ */
+AclMode
+pg_foreign_server_aclmask(Oid srv_oid, Oid roleid,
+			   			  AclMode mask, AclMaskHow how)
+{
+	AclMode		result;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+	Acl		   *acl;
+	Oid			ownerId;
+
+	Form_pg_foreign_server srvForm;
+
+	/* Bypass permission checks for superusers */
+	if (superuser_arg(roleid))
+		return mask;
+
+	/*
+	 * Must get the FDW's tuple from pg_foreign_data_wrapper
+	 */
+	tuple = SearchSysCache(FOREIGNSERVEROID,
+						   ObjectIdGetDatum(srv_oid),
+						   0, 0, 0);
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				 (errmsg("foreign server with OID %u does not exist",
+						srv_oid)));
+	srvForm = (Form_pg_foreign_server) GETSTRUCT(tuple);
+
+	/*
+	 * Normal case: get the foreign server's ACL from pg_foreign_server
+	 */
+	ownerId = srvForm->srvowner;
+
+	aclDatum = SysCacheGetAttr(FOREIGNSERVEROID, tuple,
+							   Anum_pg_foreign_server_srvacl, &isNull);
+	if (isNull)
+	{
+		/* No ACL, so build default ACL */
+		acl = acldefault(ACL_OBJECT_FOREIGN_SERVER, ownerId);
+		aclDatum = (Datum) 0;
+	}
+	else
+	{
+		/* detoast rel's ACL if necessary */
+		acl = DatumGetAclP(aclDatum);
+	}
+
+	result = aclmask(acl, roleid, ownerId, mask, how);
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	ReleaseSysCache(tuple);
+
+	return result;
+}
 
 /*
  * Exported routine for checking a user's access privileges to a table
@@ -2039,6 +2452,31 @@ pg_tablespace_aclcheck(Oid spc_oid, Oid roleid, AclMode mode)
 		return ACLCHECK_NO_PRIV;
 }
 
+/*
+ * Exported routine for checking a user's access privileges to a foreign
+ * data wrapper
+ */
+AclResult
+pg_foreign_data_wrapper_aclcheck(Oid fdw_oid, Oid roleid, AclMode mode)
+{
+	if (pg_foreign_data_wrapper_aclmask(fdw_oid, roleid, mode, ACLMASK_ANY) != 0)
+		return ACLCHECK_OK;
+	else
+		return ACLCHECK_NO_PRIV;
+}
+
+/*
+ * Exported routine for checking a user's access privileges to a foreign
+ * server
+ */
+AclResult
+pg_foreign_server_aclcheck(Oid srv_oid, Oid roleid, AclMode mode)
+{
+	if (pg_foreign_server_aclmask(srv_oid, roleid, mode, ACLMASK_ANY) != 0)
+		return ACLCHECK_OK;
+	else
+		return ACLCHECK_NO_PRIV;
+}
 
 /*
  * Ownership check for a relation (specified by OID).
@@ -2364,6 +2802,34 @@ pg_ts_config_ownercheck(Oid cfg_oid, Oid roleid)
 	return has_privs_of_role(roleid, ownerId);
 }
 
+/*
+ * Ownership check for a foreign server (specified by OID).
+ */
+bool
+pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid)
+{
+	HeapTuple	tuple;
+	Oid			ownerId;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return true;
+
+	tuple = SearchSysCache(FOREIGNSERVEROID,
+						   ObjectIdGetDatum(srv_oid),
+						   0, 0, 0);
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+			   errmsg("foreign server with OID %u does not exist",
+					  srv_oid)));
+
+	ownerId = ((Form_pg_foreign_server) GETSTRUCT(tuple))->srvowner;
+
+	ReleaseSysCache(tuple);
+
+	return has_privs_of_role(roleid, ownerId);
+}
 
 /*
  * Ownership check for a database (specified by OID).
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 8cd1abdf1b95428008ecb2f3f31036f8b07c9ce3..2cbc19f5a0621dd63be004216399a31601c45031 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.82 2008/11/10 21:49:16 alvherre Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.83 2008/12/19 16:25:17 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -33,6 +33,8 @@
 #include "catalog/pg_conversion_fn.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_depend.h"
+#include "catalog/pg_foreign_data_wrapper.h"
+#include "catalog/pg_foreign_server.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
@@ -47,6 +49,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_user_mapping.h"
 #include "commands/comment.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
@@ -55,6 +58,7 @@
 #include "commands/tablespace.h"
 #include "commands/trigger.h"
 #include "commands/typecmds.h"
+#include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
@@ -1105,6 +1109,18 @@ doDeletion(const ObjectAddress *object)
 			RemoveTSConfigurationById(object->objectId);
 			break;
 
+		case OCLASS_USER_MAPPING:
+			RemoveUserMappingById(object->objectId);
+			break;
+
+		case OCLASS_FOREIGN_SERVER:
+			RemoveForeignServerById(object->objectId);
+			break;
+
+		case OCLASS_FDW:
+			RemoveForeignDataWrapperById(object->objectId);
+			break;
+
 			/* OCLASS_ROLE, OCLASS_DATABASE, OCLASS_TBLSPACE not handled */
 
 		default:
@@ -2005,6 +2021,18 @@ getObjectClass(const ObjectAddress *object)
 		case TableSpaceRelationId:
 			Assert(object->objectSubId == 0);
 			return OCLASS_TBLSPACE;
+
+		case ForeignDataWrapperRelationId:
+			Assert(object->objectSubId == 0);
+			return OCLASS_FDW;
+
+		case ForeignServerRelationId:
+			Assert(object->objectSubId == 0);
+			return OCLASS_FOREIGN_SERVER;
+
+		case UserMappingRelationId:
+			Assert(object->objectSubId == 0);
+			return OCLASS_USER_MAPPING;
 	}
 
 	/* shouldn't get here */
@@ -2501,6 +2529,50 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_FDW:
+			{
+				ForeignDataWrapper *fdw;
+
+				fdw = GetForeignDataWrapper(object->objectId);
+				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				break;
+			}
+
+		case OCLASS_FOREIGN_SERVER:
+			{
+				ForeignServer *srv;
+
+				srv = GetForeignServer(object->objectId);
+				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				break;
+			}
+
+		case OCLASS_USER_MAPPING:
+			{
+				HeapTuple		tup;
+				Oid				useid;
+				char		   *usename;
+
+				tup = SearchSysCache(USERMAPPINGOID,
+									 ObjectIdGetDatum(object->objectId),
+									 0, 0, 0);
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for user mapping %u",
+						 object->objectId);
+
+				useid = ((Form_pg_user_mapping) GETSTRUCT(tup))->umuser;
+
+				ReleaseSysCache(tup);
+
+				if (OidIsValid(useid))
+					usename = GetUserNameFromId(useid);
+				else
+					usename = "public";
+
+				appendStringInfo(&buffer, _("user mapping for %s"), usename);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 970b48b7dff3487898e06444acca14ef6995a803..2d9cdb8e2248d8b90aa6aab554e527e0fb10ab9c 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -4,7 +4,7 @@
  *
  * Copyright (c) 2003-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/catalog/information_schema.sql,v 1.46 2008/09/08 00:47:40 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/information_schema.sql,v 1.47 2008/12/19 16:25:17 petere Exp $
  */
 
 /*
@@ -1244,19 +1244,53 @@ GRANT SELECT ON role_table_grants TO PUBLIC;
  * ROLE_USAGE_GRANTS view
  */
 
--- See USAGE_PRIVILEGES.
-
 CREATE VIEW role_usage_grants AS
-    SELECT CAST(null AS sql_identifier) AS grantor,
-           CAST(null AS sql_identifier) AS grantee,
+
+    /* foreign-data wrappers */
+    SELECT CAST(u_grantor.rolname AS sql_identifier) AS grantor,
+           CAST(g_grantee.rolname AS sql_identifier) AS grantee,
            CAST(current_database() AS sql_identifier) AS object_catalog,
-           CAST(null AS sql_identifier) AS object_schema,
-           CAST(null AS sql_identifier) AS object_name,
-           CAST(null AS character_data) AS object_type,
+           CAST('' AS sql_identifier) AS object_schema,
+           CAST(fdw.fdwname AS sql_identifier) AS object_name,
+           CAST('FOREIGN DATA WRAPPER' AS character_data) AS object_type,
            CAST('USAGE' AS character_data) AS privilege_type,
-           CAST(null AS character_data) AS is_grantable
+           CAST(
+             CASE WHEN aclcontains(fdw.fdwacl,
+                                   makeaclitem(g_grantee.oid, u_grantor.oid, 'USAGE', true))
+                  THEN 'YES' ELSE 'NO' END AS character_data) AS is_grantable
 
-    WHERE false;
+    FROM pg_foreign_data_wrapper fdw,
+         pg_authid u_grantor,
+         pg_authid g_grantee
+
+    WHERE aclcontains(fdw.fdwacl,
+                          makeaclitem(g_grantee.oid, u_grantor.oid, 'USAGE', false))
+          AND (u_grantor.rolname IN (SELECT role_name FROM enabled_roles)
+               OR g_grantee.rolname IN (SELECT role_name FROM enabled_roles))
+
+    UNION ALL
+
+    /* foreign server */
+    SELECT CAST(u_grantor.rolname AS sql_identifier) AS grantor,
+           CAST(g_grantee.rolname AS sql_identifier) AS grantee,
+           CAST(current_database() AS sql_identifier) AS object_catalog,
+           CAST('' AS sql_identifier) AS object_schema,
+           CAST(srv.srvname AS sql_identifier) AS object_name,
+           CAST('FOREIGN SERVER' AS character_data) AS object_type,
+           CAST('USAGE' AS character_data) AS privilege_type,
+           CAST(
+             CASE WHEN aclcontains(srv.srvacl,
+                                   makeaclitem(g_grantee.oid, u_grantor.oid, 'USAGE', true))
+                  THEN 'YES' ELSE 'NO' END AS character_data) AS is_grantable
+
+    FROM pg_foreign_server srv,
+         pg_authid u_grantor,
+         pg_authid g_grantee
+
+    WHERE aclcontains(srv.srvacl,
+                          makeaclitem(g_grantee.oid, u_grantor.oid, 'USAGE', false))
+          AND (u_grantor.rolname IN (SELECT role_name FROM enabled_roles)
+               OR g_grantee.rolname IN (SELECT role_name FROM enabled_roles));
 
 GRANT SELECT ON role_usage_grants TO PUBLIC;
 
@@ -2007,11 +2041,10 @@ GRANT SELECT ON triggers TO PUBLIC;
  * USAGE_PRIVILEGES view
  */
 
--- Of the things currently implemented in PostgreSQL, usage privileges
--- apply only to domains.  Since domains have no real privileges, we
--- represent all domains with implicit usage privilege here.
-
 CREATE VIEW usage_privileges AS
+
+    /* domains */
+    -- Domains have no real privileges, so we represent all domains with implicit usage privilege here.
     SELECT CAST(u.rolname AS sql_identifier) AS grantor,
            CAST('PUBLIC' AS sql_identifier) AS grantee,
            CAST(current_database() AS sql_identifier) AS object_catalog,
@@ -2027,7 +2060,65 @@ CREATE VIEW usage_privileges AS
 
     WHERE u.oid = t.typowner
           AND t.typnamespace = n.oid
-          AND t.typtype = 'd';
+          AND t.typtype = 'd'
+
+    UNION ALL
+
+    /* foreign-data wrappers */
+    SELECT CAST(u_grantor.rolname AS sql_identifier) AS grantor,
+           CAST(grantee.rolname AS sql_identifier) AS grantee,
+           CAST(current_database() AS sql_identifier) AS object_catalog,
+           CAST('' AS sql_identifier) AS object_schema,
+           CAST(fdw.fdwname AS sql_identifier) AS object_name,
+           CAST('FOREIGN DATA WRAPPER' AS character_data) AS object_type,
+           CAST('USAGE' AS character_data) AS privilege_type,
+           CAST(
+             CASE WHEN aclcontains(fdw.fdwacl,
+                                   makeaclitem(grantee.oid, u_grantor.oid, 'USAGE', true))
+                  THEN 'YES' ELSE 'NO' END AS character_data) AS is_grantable
+
+    FROM pg_foreign_data_wrapper fdw,
+         pg_authid u_grantor,
+         (
+           SELECT oid, rolname FROM pg_authid
+           UNION ALL
+           SELECT 0::oid, 'PUBLIC'
+         ) AS grantee (oid, rolname)
+
+    WHERE aclcontains(fdw.fdwacl,
+                      makeaclitem(grantee.oid, u_grantor.oid, 'USAGE', false))
+          AND (pg_has_role(u_grantor.oid, 'USAGE')
+               OR pg_has_role(grantee.oid, 'USAGE')
+               OR grantee.rolname = 'PUBLIC')
+
+    UNION ALL
+
+    /* foreign servers */
+    SELECT CAST(u_grantor.rolname AS sql_identifier) AS grantor,
+           CAST(grantee.rolname AS sql_identifier) AS grantee,
+           CAST(current_database() AS sql_identifier) AS object_catalog,
+           CAST('' AS sql_identifier) AS object_schema,
+           CAST(srv.srvname AS sql_identifier) AS object_name,
+           CAST('FOREIGN SERVER' AS character_data) AS object_type,
+           CAST('USAGE' AS character_data) AS privilege_type,
+           CAST(
+             CASE WHEN aclcontains(srv.srvacl,
+                                   makeaclitem(grantee.oid, u_grantor.oid, 'USAGE', true))
+                  THEN 'YES' ELSE 'NO' END AS character_data) AS is_grantable
+
+    FROM pg_foreign_server srv,
+         pg_authid u_grantor,
+         (
+           SELECT oid, rolname FROM pg_authid
+           UNION ALL
+           SELECT 0::oid, 'PUBLIC'
+         ) AS grantee (oid, rolname)
+
+    WHERE aclcontains(srv.srvacl,
+                      makeaclitem(grantee.oid, u_grantor.oid, 'USAGE', false))
+          AND (pg_has_role(u_grantor.oid, 'USAGE')
+               OR pg_has_role(grantee.oid, 'USAGE')
+               OR grantee.rolname = 'PUBLIC');
 
 GRANT SELECT ON usage_privileges TO PUBLIC;
 
@@ -2313,3 +2404,139 @@ CREATE VIEW element_types AS
                     FROM data_type_privileges );
 
 GRANT SELECT ON element_types TO PUBLIC;
+
+
+-- SQL/MED views; these use section numbers from part 9 of the standard.
+
+/* Base view for foreign-data wrappers */
+CREATE VIEW _pg_foreign_data_wrappers AS
+    SELECT w.oid,
+           w.fdwowner,
+           w.fdwoptions,
+           CAST(current_database() AS sql_identifier) AS foreign_data_wrapper_catalog,
+           CAST(fdwname AS sql_identifier) AS foreign_data_wrapper_name,
+           CAST(u.rolname AS sql_identifier) AS authorization_identifier,
+           CAST(fdwlibrary AS character_data) AS library_name,
+           CAST('c' AS character_data) AS foreign_data_wrapper_language
+    FROM pg_foreign_data_wrapper w, pg_authid u
+    WHERE u.oid = w.fdwowner
+          AND (pg_has_role(fdwowner, 'USAGE')
+               OR has_foreign_data_wrapper_privilege(w.oid, 'USAGE'));
+
+
+/*
+ * 24.4
+ * FOREIGN_DATA_WRAPPER_OPTIONS view
+ */
+CREATE VIEW foreign_data_wrapper_options AS
+    SELECT foreign_data_wrapper_catalog,
+           foreign_data_wrapper_name,
+           CAST((pg_options_to_table(w.fdwoptions)).option_name AS sql_identifier) AS option_name,
+           CAST((pg_options_to_table(w.fdwoptions)).option_value AS character_data) AS option_value
+    FROM _pg_foreign_data_wrappers w;
+
+GRANT SELECT ON foreign_data_wrapper_options TO PUBLIC;
+
+
+/*
+ * 24.5
+ * FOREIGN_DATA_WRAPPERS view
+ */
+CREATE VIEW foreign_data_wrappers AS
+    SELECT foreign_data_wrapper_catalog,
+           foreign_data_wrapper_name,
+           authorization_identifier,
+           library_name,
+           foreign_data_wrapper_language
+    FROM _pg_foreign_data_wrappers w;
+
+GRANT SELECT ON foreign_data_wrappers TO PUBLIC;
+
+
+/* Base view for foreign servers */
+CREATE VIEW _pg_foreign_servers AS
+    SELECT s.oid,
+           s.srvoptions,
+           CAST(current_database() AS sql_identifier) AS foreign_server_catalog,
+           CAST(srvname AS sql_identifier) AS foreign_server_name,
+           w.foreign_data_wrapper_catalog,
+           w.foreign_data_wrapper_name,
+           CAST(srvtype AS character_data) AS foreign_server_type,
+           CAST(srvversion AS character_data) AS foreign_server_version,
+           CAST(u.rolname AS sql_identifier) AS authorization_identifier
+    FROM pg_foreign_server s, _pg_foreign_data_wrappers w, pg_authid u
+    WHERE w.oid = s.srvfdw
+          AND u.oid = s.srvowner
+          AND (pg_has_role(s.srvowner, 'USAGE')
+               OR has_server_privilege(s.oid, 'USAGE'));
+
+
+/*
+ * 24.6
+ * FOREIGN_SERVER_OPTIONS view
+ */
+CREATE VIEW foreign_server_options AS
+    SELECT foreign_server_catalog,
+           foreign_server_name,
+           CAST((pg_options_to_table(s.srvoptions)).option_name AS sql_identifier) AS option_name,
+           CAST((pg_options_to_table(s.srvoptions)).option_value AS character_data) AS option_value
+    FROM _pg_foreign_servers s;
+
+GRANT SELECT ON TABLE foreign_server_options TO PUBLIC;
+
+
+/*
+ * 24.7
+ * FOREIGN_SERVERS view
+ */
+CREATE VIEW foreign_servers AS
+    SELECT foreign_server_catalog,
+           foreign_server_name,
+           foreign_data_wrapper_catalog,
+           foreign_data_wrapper_name,
+           foreign_server_type,
+           foreign_server_version,
+           authorization_identifier
+    FROM _pg_foreign_servers;
+
+GRANT SELECT ON foreign_servers TO PUBLIC;
+
+
+/* Base view for user mappings */
+CREATE VIEW _pg_user_mappings AS
+    SELECT um.oid,
+           um.umoptions,
+           CAST(COALESCE(u.rolname,'PUBLIC') AS sql_identifier ) AS authorization_identifier,
+           s.foreign_server_catalog,
+           s.foreign_server_name
+    FROM pg_user_mapping um LEFT JOIN pg_authid u ON (u.oid = um.umuser),
+         _pg_foreign_servers s
+    WHERE s.oid = um.umserver;
+
+
+/*
+ * 24.12
+ * USER_MAPPING_OPTIONS view
+ */
+CREATE VIEW user_mapping_options AS
+    SELECT authorization_identifier,
+           foreign_server_catalog,
+           foreign_server_name,
+           CAST((pg_options_to_table(um.umoptions)).option_name AS sql_identifier) AS option_name,
+           CAST((pg_options_to_table(um.umoptions)).option_value AS character_data) AS option_value
+    FROM _pg_user_mappings um;
+
+GRANT SELECT ON user_mapping_options TO PUBLIC;
+
+
+/*
+ * 24.13
+ * USER_MAPPINGS view
+ */
+CREATE VIEW user_mappings AS
+    SELECT authorization_identifier,
+           foreign_server_catalog,
+           foreign_server_name
+    FROM _pg_user_mappings;
+
+GRANT SELECT ON user_mappings TO PUBLIC;
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index 402bc5e3919020ee7c76f8715a5cded6ab7dffb6..5b387456fd3762ddaaca65fb1089821edf57f300 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -487,6 +487,31 @@ T652	SQL-dynamic statements in SQL routines			NO
 T653	SQL-schema statements in external routines			NO	
 T654	SQL-dynamic statements in external routines			NO	
 T655	Cyclically dependent routines			NO	
+M001	Datalinks			NO	
+M002	Datalinks via SQL/CLI			NO	
+M003	Datalinks via Embedded SQL			NO	
+M004	Foreign data support			NO	
+M005	Foreign schema support			NO	
+M006	GetSQLString routine			NO	
+M007	TransmitRequest			NO	
+M009	GetOpts and GetStatistics routines			NO	
+M010	Foreign data wrapper support			NO	
+M011	Datalinks via Ada			NO	
+M012	Datalinks via C			NO	
+M013	Datalinks via COBOL			NO	
+M014	Datalinks via Fortran			NO	
+M015	Datalinks via M			NO	
+M016	Datalinks via Pascal			NO	
+M017	Datalinks via PL/I			NO	
+M018	Foreign data wrapper interface routines in Ada			NO	
+M019	Foreign data wrapper interface routines in C			NO	
+M020	Foreign data wrapper interface routines in COBOL			NO	
+M021	Foreign data wrapper interface routines in Fortran			NO	
+M022	Foreign data wrapper interface routines in MUMPS			NO	
+M023	Foreign data wrapper interface routines in Pascal			NO	
+M024	Foreign data wrapper interface routines in PL/I			NO	
+M030	SQL-server foreign data support			NO	
+M031	Foreign data wrapper general routines			NO	
 X010	XML type			YES	
 X011	Arrays of XML type			YES	
 X012	Multisets of XML type			NO	
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 07c0809609d6836679f334a3bf6627d45bc5c743..4223e5d1940420c4b176429b2b020fddd6b84eca 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 1996-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/catalog/system_views.sql,v 1.56 2008/11/09 21:24:32 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/system_views.sql,v 1.57 2008/12/19 16:25:17 petere Exp $
  */
 
 CREATE VIEW pg_roles AS 
@@ -381,6 +381,28 @@ CREATE VIEW pg_stat_bgwriter AS
         pg_stat_get_buf_written_backend() AS buffers_backend,
         pg_stat_get_buf_alloc() AS buffers_alloc;
 
+CREATE VIEW pg_user_mappings AS
+    SELECT
+        U.oid       AS umid,
+        S.oid       AS srvid,
+        S.srvname   AS srvname,
+        U.umuser    AS umuser,
+        CASE WHEN U.umuser = 0 THEN
+            'public'
+        ELSE
+            A.rolname
+        END AS usename,
+        CASE WHEN pg_has_role(S.srvowner, 'USAGE') OR has_server_privilege(S.oid, 'USAGE') THEN
+            U.umoptions
+        ELSE
+            NULL
+        END AS umoptions
+    FROM pg_user_mapping U
+         LEFT JOIN pg_authid A ON (A.oid = U.umuser) JOIN
+        pg_foreign_server S ON (U.umserver = S.oid);
+
+REVOKE ALL on pg_user_mapping FROM public;
+
 -- Tsearch debug function.  Defined here because it'd be pretty unwieldy
 -- to put it into pg_proc.h
 
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 49aefde416458e3cf59ffa1e9c6206e8c09d7abd..e455e62c9aa8e69a501ae41c402a27c01abb0d75 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -4,7 +4,7 @@
 #    Makefile for backend/commands
 #
 # IDENTIFICATION
-#    $PostgreSQL: pgsql/src/backend/commands/Makefile,v 1.38 2008/02/19 10:30:07 petere Exp $
+#    $PostgreSQL: pgsql/src/backend/commands/Makefile,v 1.39 2008/12/19 16:25:17 petere Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o  \
 	conversioncmds.o copy.o \
-	dbcommands.o define.o discard.o explain.o functioncmds.o \
+	dbcommands.o define.o discard.o explain.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \
 	portalcmds.o prepare.o proclang.o \
 	schemacmds.o sequence.o tablecmds.o tablespace.o trigger.o \
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index fce328bcc404b3942f5a40359d40f17af5d16c1a..3772638766cad20ffb856ec1d18f919add28630b 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/alter.c,v 1.29 2008/06/15 01:25:53 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/alter.c,v 1.30 2008/12/19 16:25:17 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -270,6 +270,15 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 			AlterTSConfigurationOwner(stmt->object, newowner);
 			break;
 
+		case OBJECT_FDW:
+			AlterForeignDataWrapperOwner(strVal(linitial(stmt->object)),
+										 newowner);
+			break;
+
+		case OBJECT_FOREIGN_SERVER:
+			AlterForeignServerOwner(strVal(linitial(stmt->object)), newowner);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized AlterOwnerStmt type: %d",
 				 (int) stmt->objectType);
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
new file mode 100644
index 0000000000000000000000000000000000000000..224a8f004e63a0b664237172a3f28af40e5c96d7
--- /dev/null
+++ b/src/backend/commands/foreigncmds.c
@@ -0,0 +1,1102 @@
+/*-------------------------------------------------------------------------
+ *
+ * foreigncmds.c
+ *	  foreign-data wrapper/server creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  $PostgreSQL: pgsql/src/backend/commands/foreigncmds.c,v 1.1 2008/12/19 16:25:17 petere Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/reloptions.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_foreign_data_wrapper.h"
+#include "catalog/pg_foreign_server.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_user_mapping.h"
+#include "commands/defrem.h"
+#include "foreign/foreign.h"
+#include "miscadmin.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+
+/*
+ * Convert a DefElem list to the text array format that is used in
+ * pg_foreign_data_wrapper, pg_foreign_server, and pg_user_mapping.
+ *
+ * Note: The array is usually stored to database without further
+ * processing, hence any validation should be done before this
+ * conversion.
+ */
+static Datum
+optionListToArray(List *options)
+{
+	ArrayBuildState    *astate = NULL;
+	ListCell		   *cell;
+	text			   *t;
+
+	foreach (cell, options)
+	{
+		DefElem    *def = lfirst(cell);
+		const char *value = "";
+		Size		len;
+
+		value = defGetString(def);
+		len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value);
+		t = palloc(len + 1);
+		SET_VARSIZE(t, len);
+		sprintf(VARDATA(t), "%s=%s", def->defname, value);
+
+		astate = accumArrayResult(astate, PointerGetDatum(t),
+								  false, TEXTOID,
+								  CurrentMemoryContext);
+	}
+
+	if (astate)
+		return makeArrayResult(astate, CurrentMemoryContext);
+
+	return PointerGetDatum(NULL);
+}
+
+
+/*
+ * Transform the list of OptionDefElem into list of generic options.
+ * The result is converted to array of text suitable for storing in
+ * options.
+ *
+ * This is used by CREATE/ALTER of FOREIGN DATA WRAPPER/SERVER/USER
+ * MAPPING.  In the ALTER cases, oldOptions is the current text array
+ * of options.
+ */
+static Datum
+transformGenericOptions(Datum oldOptions,
+						List *optionDefList,
+						GenericOptionFlags flags,
+						ForeignDataWrapper *fdw,
+						OptionListValidatorFunc validateOptionList)
+{
+	List	 *resultOptions = untransformRelOptions(oldOptions);
+	ListCell *optcell;
+
+	foreach (optcell, optionDefList)
+	{
+		ListCell	    *cell;
+		ListCell		*prev = NULL;
+		OptionDefElem	*od = lfirst(optcell);
+
+		/*
+		 * Find the element in resultOptions.  We need this for
+		 * validation in all cases.
+		 */
+		foreach (cell, resultOptions)
+		{
+			DefElem	*def = lfirst(cell);
+
+			if (strcmp(def->defname, od->def->defname) == 0)
+				break;
+			else
+				prev = cell;
+		}
+
+		/*
+		 * It is possible to perform multiple SET/DROP actions on the
+		 * same option.  The standard permits this, as long as the
+		 * options to be added are unique.
+		 */
+
+		switch (od->alter_op)
+		{
+			case ALTER_OPT_DROP:
+				if (!cell)
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_OBJECT),
+							 errmsg("option \"%s\" not found",
+									od->def->defname)));
+				resultOptions = list_delete_cell(resultOptions, cell, prev);
+				break;
+
+			case ALTER_OPT_SET:
+				if (!cell)
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_OBJECT),
+							 errmsg("option \"%s\" not found",
+									od->def->defname)));
+				lfirst(cell) = od->def;
+				break;
+
+			case ALTER_OPT_ADD:
+				if (cell)
+					ereport(ERROR,
+							(errcode(ERRCODE_DUPLICATE_OBJECT),
+							 errmsg("option \"%s\" provided more than once",
+									od->def->defname)));
+				resultOptions = lappend(resultOptions, od->def);
+				break;
+
+			default:
+				elog(ERROR, "unrecognized action %d on option \"%s\"",
+					 od->alter_op, od->def->defname);
+				break;
+		}
+	}
+
+	if (validateOptionList)
+		validateOptionList(fdw, flags, resultOptions);
+
+	return optionListToArray(resultOptions);
+}
+
+
+/*
+ * Convert the user mapping user name to OID
+ */
+static Oid
+GetUserOidFromMapping(const char *username, bool missing_ok)
+{
+    if (!username)
+        /* PUBLIC user mapping */
+        return InvalidOid;
+
+    if (strcmp(username, "current_user") == 0)
+        /* map to the owner */
+        return GetUserId();
+
+    /* map to provided user */
+    return missing_ok ? get_roleid(username) : get_roleid_checked(username);
+}
+
+
+/*
+ * Change foreign-data wrapper owner.
+ *
+ * Allow this only for superusers; also the new owner must be a
+ * superuser.
+ */
+void
+AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId)
+{
+	HeapTuple	tup;
+	Relation	rel;
+	Oid			fdwId;
+
+	Form_pg_foreign_data_wrapper form;
+
+    /* Must be a superuser to change a FDW owner */
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied to change owner of foreign-data wrapper \"%s\"",
+						name),
+				 errhint("Must be superuser to change owner of a foreign-data wrapper.")));
+
+    /* New owner must also be a superuser */
+	if (!superuser_arg(newOwnerId))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied to change owner of foreign-data wrapper \"%s\"",
+						name),
+				 errhint("The owner of a foreign-data wrapper must be a superuser.")));
+
+	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+	tup = SearchSysCacheCopy(FOREIGNDATAWRAPPERNAME,
+							 CStringGetDatum(name),
+							 0, 0, 0);
+
+	if (!HeapTupleIsValid(tup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("foreign-data wrapper \"%s\" does not exist", name)));
+
+	fdwId = HeapTupleGetOid(tup);
+	form = (Form_pg_foreign_data_wrapper) GETSTRUCT(tup);
+
+	if (form->fdwowner != newOwnerId)
+	{
+		form->fdwowner = newOwnerId;
+
+		simple_heap_update(rel, &tup->t_self, tup);
+		CatalogUpdateIndexes(rel, tup);
+
+		/* Update owner dependency reference */
+		changeDependencyOnOwner(ForeignDataWrapperRelationId,
+								fdwId,
+								newOwnerId);
+	}
+
+	heap_close(rel, NoLock);
+	heap_freetuple(tup);
+}
+
+
+/*
+ * Change foreign server owner
+ */
+void
+AlterForeignServerOwner(const char *name, Oid newOwnerId)
+{
+	HeapTuple	tup;
+	Relation	rel;
+	Oid			srvId;
+	AclResult	aclresult;
+	Form_pg_foreign_server form;
+
+	rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
+
+	tup = SearchSysCacheCopy(FOREIGNSERVERNAME,
+							 CStringGetDatum(name),
+							 0, 0, 0);
+
+	if (!HeapTupleIsValid(tup))
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                errmsg("server \"%s\" does not exist", name)));
+
+	srvId = HeapTupleGetOid(tup);
+	form = (Form_pg_foreign_server) GETSTRUCT(tup);
+
+	if (form->srvowner != newOwnerId)
+	{
+		/* Superusers can always do it */
+		if (!superuser())
+		{
+			/* Must be owner */
+			if (!pg_foreign_server_ownercheck(srvId, GetUserId()))
+				aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER,
+							   name);
+
+			/* Must be able to become new owner */
+			check_is_member_of_role(GetUserId(), newOwnerId);
+
+			/* New owner must have USAGE privilege on foreign-data wrapper */
+			aclresult = pg_foreign_data_wrapper_aclcheck(form->srvfdw, newOwnerId, ACL_USAGE);
+			if (aclresult != ACLCHECK_OK)
+			{
+				ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw);
+
+				aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname);
+			}
+		}
+
+		form->srvowner = newOwnerId;
+
+		simple_heap_update(rel, &tup->t_self, tup);
+		CatalogUpdateIndexes(rel, tup);
+
+		/* Update owner dependency reference */
+		changeDependencyOnOwner(ForeignServerRelationId, HeapTupleGetOid(tup),
+								newOwnerId);
+	}
+
+	heap_close(rel, NoLock);
+	heap_freetuple(tup);
+}
+
+
+/*
+ * Create a foreign-data wrapper
+ */
+void
+CreateForeignDataWrapper(CreateFdwStmt *stmt)
+{
+	Relation		rel;
+	Datum			values[Natts_pg_foreign_data_wrapper];
+	bool			nulls[Natts_pg_foreign_data_wrapper];
+	HeapTuple		tuple;
+	Oid				fdwId;
+	Datum			fdwoptions = InvalidOid;
+	Oid				ownerId;
+	ForeignDataWrapperLibrary  *fdwlib;
+
+	/* Must be super user */
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied to create foreign-data wrapper \"%s\"",
+						stmt->fdwname),
+				 errhint("Must be superuser to create a foreign-data wrapper.")));
+
+	/* For now the owner cannot be specified on create. Use effective user ID. */
+	ownerId = GetUserId();
+
+	/*
+	 * Check that there is no other foreign-data wrapper by this name.
+	 */
+	if (GetForeignDataWrapperByName(stmt->fdwname, true) != NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("foreign-data wrapper \"%s\" already exists",
+						stmt->fdwname)));
+
+	/*
+	 * Insert tuple into pg_foreign_data_wrapper.
+	 */
+	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, Natts_pg_foreign_data_wrapper);
+
+	values[Anum_pg_foreign_data_wrapper_fdwname - 1] =
+		DirectFunctionCall1(namein, CStringGetDatum(stmt->fdwname));
+	values[Anum_pg_foreign_data_wrapper_fdwowner - 1] = ObjectIdGetDatum(ownerId);
+	values[Anum_pg_foreign_data_wrapper_fdwlibrary - 1] = CStringGetTextDatum(stmt->library);
+	nulls[Anum_pg_foreign_data_wrapper_fdwacl - 1] = true;
+
+	/*
+	 * See if the FDW library loads at all. We also might want to use it
+	 * later for validating the options.
+	 */
+	fdwlib = GetForeignDataWrapperLibrary(stmt->library);
+
+	fdwoptions = transformGenericOptions(0, stmt->options, FdwOpt, NULL,
+										 fdwlib->validateOptionList);
+
+	if (OidIsValid(fdwoptions))
+		values[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = fdwoptions;
+	else
+		nulls[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true;
+
+	tuple = heap_form_tuple(rel->rd_att, values, nulls);
+
+	fdwId = simple_heap_insert(rel, tuple);
+	CatalogUpdateIndexes(rel, tuple);
+
+	heap_freetuple(tuple);
+
+	recordDependencyOnOwner(ForeignDataWrapperRelationId, fdwId, ownerId);
+
+	heap_close(rel, NoLock);
+}
+
+
+/*
+ * Alter foreign-data wrapper
+ */
+void
+AlterForeignDataWrapper(AlterFdwStmt *stmt)
+{
+	Relation	rel;
+	HeapTuple	tp;
+	Datum		repl_val[Natts_pg_foreign_data_wrapper];
+	bool		repl_null[Natts_pg_foreign_data_wrapper];
+	bool		repl_repl[Natts_pg_foreign_data_wrapper];
+	Oid			fdwId;
+	bool		isnull;
+	Datum		datum;
+	ForeignDataWrapperLibrary *fdwlib;
+
+	/* Must be super user */
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied to alter foreign-data wrapper \"%s\"",
+						stmt->fdwname),
+				 errhint("Must be superuser to alter a foreign-data wrapper.")));
+
+	tp = SearchSysCacheCopy(FOREIGNDATAWRAPPERNAME,
+							CStringGetDatum(stmt->fdwname),
+							0, 0, 0);
+
+	if (!HeapTupleIsValid(tp))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("foreign-data wrapper \"%s\" does not exist", stmt->fdwname)));
+
+	fdwId = HeapTupleGetOid(tp);
+
+	memset(repl_val, 0, sizeof(repl_val));
+	memset(repl_null, false, sizeof(repl_null));
+	memset(repl_repl, false, sizeof(repl_repl));
+
+	if (stmt->library)
+	{
+		/*
+		 * New library specified -- load to see if valid.
+		 */
+		fdwlib = GetForeignDataWrapperLibrary(stmt->library);
+
+		repl_val[Anum_pg_foreign_data_wrapper_fdwlibrary - 1] = CStringGetTextDatum(stmt->library);
+		repl_repl[Anum_pg_foreign_data_wrapper_fdwlibrary - 1] = true;
+
+		/*
+		 * It could be that the options for the FDW, SERVER and USER MAPPING
+		 * are no longer valid with the new library.  Warn about this.
+		 */
+		ereport(WARNING,
+				(errmsg("changing the foreign-data wrapper library can cause "
+						"the options for dependent objects to become invalid")));
+	}
+	else
+	{
+		/*
+		 * No LIBRARY clause specified, but we need to load it for validating
+		 * options.
+		 */
+		datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
+								tp,
+								Anum_pg_foreign_data_wrapper_fdwlibrary,
+								&isnull);
+		fdwlib = GetForeignDataWrapperLibrary(TextDatumGetCString(datum));
+	}
+
+	/*
+	 * Options specified, validate and update.
+	 */
+	if (stmt->options)
+	{
+		/* Extract the current options */
+		datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
+								tp,
+								Anum_pg_foreign_data_wrapper_fdwoptions,
+								&isnull);
+
+		/* Transform the options */
+		datum = transformGenericOptions(datum, stmt->options, FdwOpt,
+										NULL, fdwlib->validateOptionList);
+
+		if (OidIsValid(datum))
+			repl_val[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = ObjectIdGetDatum(datum);
+		else
+			repl_null[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true;
+
+		repl_repl[Anum_pg_foreign_data_wrapper_fdwoptions - 1] = true;
+	}
+
+	/* Everything looks good - update the tuple */
+
+	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+	tp = heap_modify_tuple(tp, RelationGetDescr(rel),
+						  repl_val, repl_null, repl_repl);
+
+	simple_heap_update(rel, &tp->t_self, tp);
+	CatalogUpdateIndexes(rel, tp);
+
+	heap_close(rel, RowExclusiveLock);
+	heap_freetuple(tp);
+}
+
+
+/*
+ * Drop foreign-data wrapper
+ */
+void
+RemoveForeignDataWrapper(DropFdwStmt *stmt)
+{
+	Oid					fdwId;
+	ObjectAddress		object;
+
+	fdwId = GetForeignDataWrapperOidByName(stmt->fdwname, true);
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				errmsg("permission denied to drop foreign-data wrapper \"%s\"",
+					stmt->fdwname),
+				errhint("Must be superuser to drop a foreign-data wrapper.")));
+
+	if (!OidIsValid(fdwId))
+	{
+		if (!stmt->missing_ok)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("foreign-data wrapper \"%s\" does not exist",
+					stmt->fdwname)));
+
+		/* IF EXISTS specified, just note it */
+		ereport(NOTICE,
+				(errmsg("foreign-data wrapper \"%s\" does not exist, skipping",
+					stmt->fdwname)));
+		return;
+	}
+
+	/*
+	 * Do the deletion
+	 */
+	object.classId = ForeignDataWrapperRelationId;
+	object.objectId = fdwId;
+	object.objectSubId = 0;
+
+	performDeletion(&object, stmt->behavior);
+}
+
+
+/*
+ * Drop foreign-data wrapper by OID
+ */
+void
+RemoveForeignDataWrapperById(Oid fdwId)
+{
+	HeapTuple	tp;
+	Relation	rel;
+
+	rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock);
+
+	tp = SearchSysCache(FOREIGNDATAWRAPPEROID,
+						ObjectIdGetDatum(fdwId),
+						0, 0, 0);
+
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwId);
+
+	simple_heap_delete(rel, &tp->t_self);
+
+	ReleaseSysCache(tp);
+
+	heap_close(rel, RowExclusiveLock);
+}
+
+
+/*
+ * Create a foreign server
+ */
+void
+CreateForeignServer(CreateForeignServerStmt *stmt)
+{
+	Relation		rel;
+	Datum			srvoptions = InvalidOid;
+	Datum			values[Natts_pg_foreign_server];
+	bool			nulls[Natts_pg_foreign_server];
+	HeapTuple		tuple;
+	Oid				srvId;
+	Oid				ownerId;
+	AclResult		aclresult;
+	ObjectAddress 	myself;
+	ObjectAddress	referenced;
+	ForeignDataWrapper *fdw;
+
+	/* For now the owner cannot be specified on create. Use effective user ID. */
+	ownerId = GetUserId();
+
+	/*
+	 * Check that there is no other foreign server by this name.
+	 */
+	if (GetForeignServerByName(stmt->servername, true) != NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("server \"%s\" already exists",
+						stmt->servername)));
+
+	/*
+	 * Check that the FDW exists and that we have USAGE on it.
+	 * Also get the actual FDW for option validation etc.
+	 */
+	fdw = GetForeignDataWrapperByName(stmt->fdwname, false);
+
+	aclresult = pg_foreign_data_wrapper_aclcheck(fdw->fdwid, ownerId, ACL_USAGE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname);
+
+	/*
+	 * Insert tuple into pg_foreign_server.
+	 */
+	rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, Natts_pg_foreign_server);
+
+	values[Anum_pg_foreign_server_srvname - 1] =
+		DirectFunctionCall1(namein, CStringGetDatum(stmt->servername));
+	values[Anum_pg_foreign_server_srvowner - 1] = ObjectIdGetDatum(ownerId);
+	values[Anum_pg_foreign_server_srvfdw - 1] = ObjectIdGetDatum(fdw->fdwid);
+
+	/* Add server type if supplied */
+	if (stmt->servertype)
+		values[Anum_pg_foreign_server_srvtype - 1] =
+			CStringGetTextDatum(stmt->servertype);
+	else
+		nulls[Anum_pg_foreign_server_srvtype - 1] = true;
+
+	/* Add server version if supplied */
+	if (stmt->version)
+		values[Anum_pg_foreign_server_srvversion - 1] =
+			CStringGetTextDatum(stmt->version);
+	else
+		nulls[Anum_pg_foreign_server_srvversion - 1] = true;
+
+	/* Start with a blank acl */
+	nulls[Anum_pg_foreign_server_srvacl - 1] = true;
+
+	/* Add server options */
+	srvoptions = transformGenericOptions(0, stmt->options, ServerOpt, fdw,
+										 fdw->lib->validateOptionList);
+
+	if (OidIsValid(srvoptions))
+		values[Anum_pg_foreign_server_srvoptions - 1] = srvoptions;
+	else
+		nulls[Anum_pg_foreign_server_srvoptions - 1] = true;
+
+	tuple = heap_form_tuple(rel->rd_att, values, nulls);
+
+	srvId = simple_heap_insert(rel, tuple);
+
+	CatalogUpdateIndexes(rel, tuple);
+
+	heap_freetuple(tuple);
+
+	/* Add dependency on FDW and owner */
+	myself.classId = ForeignServerRelationId;
+	myself.objectId = srvId;
+	myself.objectSubId = 0;
+
+	referenced.classId = ForeignDataWrapperRelationId;
+	referenced.objectId = fdw->fdwid;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	recordDependencyOnOwner(ForeignServerRelationId, srvId, ownerId);
+
+	heap_close(rel, NoLock);
+}
+
+
+/*
+ * Alter foreign server
+ */
+void
+AlterForeignServer(AlterForeignServerStmt *stmt)
+{
+	Relation	rel;
+	HeapTuple	tp;
+	Datum		repl_val[Natts_pg_foreign_server];
+	bool		repl_null[Natts_pg_foreign_server];
+	bool		repl_repl[Natts_pg_foreign_server];
+	Oid			srvId;
+	Form_pg_foreign_server	srvForm;
+
+	tp = SearchSysCacheCopy(FOREIGNSERVERNAME,
+							CStringGetDatum(stmt->servername),
+							0, 0, 0);
+
+	if (!HeapTupleIsValid(tp))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("server \"%s\" does not exist", stmt->servername)));
+
+	srvId = HeapTupleGetOid(tp);
+	srvForm = (Form_pg_foreign_server) GETSTRUCT(tp);
+
+	/*
+	 * Only owner or a superuser can ALTER a SERVER.
+	 */
+	if (!pg_foreign_server_ownercheck(srvId, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER,
+					   stmt->servername);
+
+	memset(repl_val, 0, sizeof(repl_val));
+	memset(repl_null, false, sizeof(repl_null));
+	memset(repl_repl, false, sizeof(repl_repl));
+
+	if (stmt->has_version)
+	{
+		/*
+		 * Change the server VERSION string.
+		 */
+		if (stmt->version)
+			repl_val[Anum_pg_foreign_server_srvversion - 1] =
+				CStringGetTextDatum(stmt->version);
+		else
+			repl_null[Anum_pg_foreign_server_srvversion - 1] = true;
+
+		repl_repl[Anum_pg_foreign_server_srvversion - 1] = true;
+	}
+
+	if (stmt->options)
+	{
+		ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw);
+		Datum				datum;
+		bool				isnull;
+
+		/* Extract the current srvoptions */
+		datum = SysCacheGetAttr(FOREIGNSERVEROID,
+								tp,
+								Anum_pg_foreign_server_srvoptions,
+								&isnull);
+
+		/* Prepare the options array */
+		datum = transformGenericOptions(datum, stmt->options, ServerOpt,
+										fdw, fdw->lib->validateOptionList);
+
+		if (OidIsValid(datum))
+			repl_val[Anum_pg_foreign_server_srvoptions - 1] = datum;
+		else
+			repl_null[Anum_pg_foreign_server_srvoptions - 1] = true;
+
+		repl_repl[Anum_pg_foreign_server_srvoptions - 1] = true;
+	}
+
+	/* Everything looks good - update the tuple */
+
+	rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
+
+	tp = heap_modify_tuple(tp, RelationGetDescr(rel),
+						  repl_val, repl_null, repl_repl);
+
+	simple_heap_update(rel, &tp->t_self, tp);
+	CatalogUpdateIndexes(rel, tp);
+
+	heap_close(rel, RowExclusiveLock);
+	heap_freetuple(tp);
+}
+
+
+/*
+ * Drop foreign server
+ */
+void
+RemoveForeignServer(DropForeignServerStmt *stmt)
+{
+	Oid				srvId;
+	ObjectAddress	object;
+
+	srvId = GetForeignServerOidByName(stmt->servername, true);
+
+	if (!OidIsValid(srvId))
+	{
+		/* Server not found, complain or notice */
+		if (!stmt->missing_ok)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("server \"%s\" does not exist", stmt->servername)));
+
+		/* IF EXISTS specified, just note it */
+		ereport(NOTICE,
+				(errmsg("server \"%s\" does not exist, skipping",
+						stmt->servername)));
+		return;
+	}
+
+	/* Only allow DROP if the server is owned by the user. */
+	if (!pg_foreign_server_ownercheck(srvId, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER,
+					   stmt->servername);
+
+	object.classId = ForeignServerRelationId;
+	object.objectId = srvId;
+	object.objectSubId = 0;
+
+	performDeletion(&object, stmt->behavior);
+}
+
+
+/*
+ * Drop foreign server by OID
+ */
+void
+RemoveForeignServerById(Oid srvId)
+{
+	HeapTuple	tp;
+	Relation	rel;
+
+	rel = heap_open(ForeignServerRelationId, RowExclusiveLock);
+
+	tp = SearchSysCache(FOREIGNSERVEROID,
+						ObjectIdGetDatum(srvId),
+						0, 0, 0);
+
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for foreign server %u", srvId);
+
+	simple_heap_delete(rel, &tp->t_self);
+
+	ReleaseSysCache(tp);
+
+	heap_close(rel, RowExclusiveLock);
+}
+
+
+/*
+ * Create user mapping
+ */
+void
+CreateUserMapping(CreateUserMappingStmt *stmt)
+{
+	Relation			rel;
+	Datum				useoptions = InvalidOid;
+	Datum				values[Natts_pg_user_mapping];
+	bool				nulls[Natts_pg_user_mapping];
+	HeapTuple			tuple;
+	Oid					useId;
+	Oid					umId;
+	Oid					ownerId;
+	ObjectAddress 		myself;
+	ObjectAddress		referenced;
+	ForeignServer	   *srv;
+	ForeignDataWrapper *fdw;
+
+	ownerId = GetUserId();
+
+	useId = GetUserOidFromMapping(stmt->username, false);
+
+	/*
+	 * Check that the server exists and that the we own it.
+	 */
+	srv = GetForeignServerByName(stmt->servername, false);
+
+	if (!pg_foreign_server_ownercheck(srv->serverid, ownerId))
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER,
+					   stmt->servername);
+
+	/*
+	 * Check that the user mapping is unique within server.
+	 */
+	umId = GetSysCacheOid(USERMAPPINGUSERSERVER,
+						   ObjectIdGetDatum(useId),
+						   ObjectIdGetDatum(srv->serverid),
+						   0, 0);
+	if (OidIsValid(umId))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("user mapping \"%s\" already exists for server %s",
+						MappingUserName(useId),
+						stmt->servername)));
+
+	fdw = GetForeignDataWrapper(srv->fdwid);
+
+	/*
+	 * Insert tuple into pg_user_mapping.
+	 */
+	rel = heap_open(UserMappingRelationId, RowExclusiveLock);
+
+	MemSet(nulls, false, Natts_pg_user_mapping);
+
+	values[Anum_pg_user_mapping_umuser - 1] = ObjectIdGetDatum(useId);
+	values[Anum_pg_user_mapping_umserver - 1] = ObjectIdGetDatum(srv->serverid);
+
+	/* Add user options */
+	useoptions = transformGenericOptions(0, stmt->options, UserMappingOpt,
+										 fdw, fdw->lib->validateOptionList);
+
+	if (OidIsValid(useoptions))
+		values[Anum_pg_user_mapping_umoptions - 1] = useoptions;
+	else
+		nulls[Anum_pg_user_mapping_umoptions - 1] = true;
+
+	tuple = heap_form_tuple(rel->rd_att, values, nulls);
+
+	umId = simple_heap_insert(rel, tuple);
+
+	CatalogUpdateIndexes(rel, tuple);
+
+	heap_freetuple(tuple);
+
+	/* Add dependency on the server */
+	myself.classId = UserMappingRelationId;
+	myself.objectId = umId;
+	myself.objectSubId = 0;
+
+	referenced.classId = ForeignServerRelationId;
+	referenced.objectId = srv->serverid;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	if (OidIsValid(useId))
+		/* Record the mapped user dependency */
+		recordDependencyOnOwner(UserMappingRelationId, umId, useId);
+
+	heap_close(rel, NoLock);
+}
+
+
+/*
+ * Alter user mapping
+ */
+void
+AlterUserMapping(AlterUserMappingStmt *stmt)
+{
+	Relation	rel;
+	HeapTuple	tp;
+	Datum		repl_val[Natts_pg_user_mapping];
+	bool		repl_null[Natts_pg_user_mapping];
+	bool		repl_repl[Natts_pg_user_mapping];
+	Oid			useId;
+	Oid			umId;
+	ForeignServer	*srv;
+
+	useId = GetUserOidFromMapping(stmt->username, false);
+	srv = GetForeignServerByName(stmt->servername, false);
+
+	umId = GetSysCacheOid(USERMAPPINGUSERSERVER,
+						  ObjectIdGetDatum(useId),
+						  ObjectIdGetDatum(srv->serverid),
+						  0, 0);
+	if (!OidIsValid(umId))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("user mapping \"%s\" does not exist for the server",
+					MappingUserName(useId))));
+	}
+
+	/*
+	 * Must be owner of the server to alter user mapping.
+	 */
+	if (!pg_foreign_server_ownercheck(srv->serverid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER,
+					   stmt->servername);
+
+	tp = SearchSysCacheCopy(USERMAPPINGOID,
+							ObjectIdGetDatum(umId),
+							0, 0, 0);
+
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for user mapping %u", umId);
+
+	memset(repl_val, 0, sizeof(repl_val));
+	memset(repl_null, false, sizeof(repl_null));
+	memset(repl_repl, false, sizeof(repl_repl));
+
+	if (stmt->options)
+	{
+		ForeignDataWrapper		   *fdw;
+		Datum						datum;
+		bool						isnull;
+
+		/*
+		 * Process the options.
+		 */
+
+		fdw = GetForeignDataWrapper(srv->fdwid);
+
+		datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
+								tp,
+								Anum_pg_user_mapping_umoptions,
+								&isnull);
+
+		/* Prepare the options array */
+		datum = transformGenericOptions(datum, stmt->options, UserMappingOpt,
+										fdw, fdw->lib->validateOptionList);
+
+		if (OidIsValid(datum))
+			repl_val[Anum_pg_user_mapping_umoptions - 1] = datum;
+		else
+			repl_null[Anum_pg_user_mapping_umoptions - 1] = true;
+
+		repl_repl[Anum_pg_user_mapping_umoptions - 1] = true;
+	}
+
+	/* Everything looks good - update the tuple */
+
+	rel = heap_open(UserMappingRelationId, RowExclusiveLock);
+
+	tp = heap_modify_tuple(tp, RelationGetDescr(rel),
+						  repl_val, repl_null, repl_repl);
+
+	simple_heap_update(rel, &tp->t_self, tp);
+	CatalogUpdateIndexes(rel, tp);
+
+	heap_close(rel, RowExclusiveLock);
+	heap_freetuple(tp);
+}
+
+
+/*
+ * Drop user mapping
+ */
+void
+RemoveUserMapping(DropUserMappingStmt *stmt)
+{
+	ObjectAddress	object;
+	Oid				useId;
+	Oid				umId;
+	ForeignServer  *srv;
+
+	useId = GetUserOidFromMapping(stmt->username, stmt->missing_ok);
+	srv = GetForeignServerByName(stmt->servername, true);
+
+	if (stmt->username && !OidIsValid(useId))
+	{
+		/*
+		 * IF EXISTS specified, role not found and not public.
+		 * Notice this and leave.
+		 */
+		elog(NOTICE, "role \"%s\" does not exist, skipping", stmt->username);
+		return;
+	}
+
+	if (!srv)
+	{
+		if (!stmt->missing_ok)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("server \"%s\" does not exist",
+					stmt->servername)));
+		/* IF EXISTS, just note it */
+		ereport(NOTICE, (errmsg("server does not exist, skipping")));
+		return;
+	}
+
+	umId = GetSysCacheOid(USERMAPPINGUSERSERVER,
+						  ObjectIdGetDatum(useId),
+						  ObjectIdGetDatum(srv->serverid),
+						  0, 0);
+
+	if (!OidIsValid(umId))
+	{
+		if (!stmt->missing_ok)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("user mapping \"%s\" does not exist for the server",
+						MappingUserName(useId))));
+
+		/* IF EXISTS specified, just note it */
+		ereport(NOTICE,
+				(errmsg("user mapping \"%s\" does not exist for the server, skipping",
+					MappingUserName(useId))));
+		return;
+	}
+
+	/*
+	 * Only allow DROP if we own the server.
+	 */
+	if (!pg_foreign_server_ownercheck(srv->serverid, GetUserId()))
+	{
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER,
+					   srv->servername);
+	}
+
+	/*
+	 * Do the deletion
+	 */
+	object.classId = UserMappingRelationId;
+	object.objectId = umId;
+	object.objectSubId = 0;
+
+	performDeletion(&object, DROP_CASCADE);
+}
+
+
+/*
+ * Drop user mapping by OID.  This is called to clean up dependencies.
+ */
+void
+RemoveUserMappingById(Oid umId)
+{
+	HeapTuple	tp;
+	Relation	rel;
+
+	rel = heap_open(UserMappingRelationId, RowExclusiveLock);
+
+	tp = SearchSysCache(USERMAPPINGOID,
+						ObjectIdGetDatum(umId),
+						0, 0, 0);
+
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for user mapping %u", umId);
+
+	simple_heap_delete(rel, &tp->t_self);
+
+	ReleaseSysCache(tp);
+
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/foreign/Makefile b/src/backend/foreign/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..b3cc209c1611feefaaba5381aa84c4f860d9bc6d
--- /dev/null
+++ b/src/backend/foreign/Makefile
@@ -0,0 +1,25 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for foreign
+#
+# IDENTIFICATION
+#    $PostgreSQL: pgsql/src/backend/foreign/Makefile,v 1.1 2008/12/19 16:25:17 petere Exp $
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/foreign
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS= foreign.o
+
+include $(top_srcdir)/src/backend/common.mk
+
+FDW = dummy postgresql
+
+$(addsuffix -fdw,all install installdirs uninstall distprep):
+	for dir in $(FDW); do $(MAKE) -C $$dir `echo $@ | sed 's/-fdw$$//'` || exit; done
+
+clean distclean maintainer-clean:
+	for dir in $(FDW); do $(MAKE) -C $$dir $@ || exit; done
diff --git a/src/backend/foreign/dummy/Makefile b/src/backend/foreign/dummy/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..8a05ada0197eaf37ca5d64757a29af29b1225762
--- /dev/null
+++ b/src/backend/foreign/dummy/Makefile
@@ -0,0 +1,27 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for dummy foreign-data wrapper
+#
+# IDENTIFICATION
+#    $PostgreSQL: pgsql/src/backend/foreign/dummy/Makefile,v 1.1 2008/12/19 16:25:17 petere Exp $
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/foreign/dummy
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+NAME = dummy_fdw
+OBJS = dummy_fdw.o
+
+include $(top_srcdir)/src/Makefile.shlib
+
+all: all-shared-lib
+
+install: all install-lib
+
+installdirs: installdirs-lib
+
+clean distclean maintainer-clean: clean-lib
+	rm -f $(OBJS)
diff --git a/src/backend/foreign/dummy/dummy_fdw.c b/src/backend/foreign/dummy/dummy_fdw.c
new file mode 100644
index 0000000000000000000000000000000000000000..2017c847136b938efcc01c66ed4039306627ce0c
--- /dev/null
+++ b/src/backend/foreign/dummy/dummy_fdw.c
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * dummy_fdw.c
+ *        "dummy" foreign-data wrapper
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *        $PostgreSQL: pgsql/src/backend/foreign/dummy/dummy_fdw.c,v 1.1 2008/12/19 16:25:17 petere Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "foreign/foreign.h"
+
+PG_MODULE_MAGIC;
+
+/*
+ * This looks like a complete waste right now, but it is useful for
+ * testing, and will become more interesting as more parts of the
+ * interface are implemented.
+ */
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
new file mode 100644
index 0000000000000000000000000000000000000000..2fdb84ac1b4b6ae98879115b20bc359a47b2e454
--- /dev/null
+++ b/src/backend/foreign/foreign.c
@@ -0,0 +1,389 @@
+/*-------------------------------------------------------------------------
+ *
+ * foreign.c
+ *        support for foreign-data wrappers, servers and user mappings.
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *        $PostgreSQL: pgsql/src/backend/foreign/foreign.c,v 1.1 2008/12/19 16:25:17 petere Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/reloptions.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_foreign_data_wrapper.h"
+#include "catalog/pg_foreign_server.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_user_mapping.h"
+#include "foreign/foreign.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/parsenodes.h"
+#include "utils/acl.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+
+
+extern Datum pg_options_to_table(PG_FUNCTION_ARGS);
+
+
+/* list of currently loaded foreign-data wrapper interfaces */
+static List *loaded_fdw_interfaces = NIL;
+
+
+/*
+ * GetForeignDataWrapperLibrary - return the named FDW library.  If it
+ * is already loaded, use that.  Otherwise allocate, initialize, and
+ * store in cache.
+ */
+ForeignDataWrapperLibrary *
+GetForeignDataWrapperLibrary(const char *libname)
+{
+	MemoryContext					oldcontext;
+	void		   				   *libhandle = NULL;
+	ForeignDataWrapperLibrary	   *fdwl = NULL;
+	ListCell		   			   *cell;
+
+	/* See if we have the FDW library is already loaded */
+	foreach (cell, loaded_fdw_interfaces)
+	{
+		fdwl = lfirst(cell);
+		if (strcmp(fdwl->libname, libname) == 0)
+			return fdwl;
+	}
+
+	/*
+	 * We don't have it yet, so load and add.  Attempt a load_file()
+	 * first to filter out any missing or unloadable libraries.
+	 */
+	load_file(libname, false);
+
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	fdwl = palloc(sizeof(*fdwl));
+	fdwl->libname = pstrdup(libname);
+	loaded_fdw_interfaces = lappend(loaded_fdw_interfaces, fdwl);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/*
+	 * Now look up the foreign data wrapper functions.
+	 */
+#define LOOKUP_FUNCTION(name) \
+	(void *)(libhandle ? \
+		lookup_external_function(libhandle, name) \
+		: load_external_function(fdwl->libname, name, false, &libhandle))
+
+	fdwl->validateOptionList = LOOKUP_FUNCTION("_pg_validateOptionList");
+
+	return fdwl;
+}
+
+
+/*
+ * GetForeignDataWrapper -  look up the foreign-data wrapper by OID.
+ *
+ * Here we also deal with loading the FDW library and looking up the
+ * actual functions.
+ */
+ForeignDataWrapper *
+GetForeignDataWrapper(Oid fdwid)
+{
+	Form_pg_foreign_data_wrapper	fdwform;
+	ForeignDataWrapper			   *fdw;
+	Datum							datum;
+	HeapTuple						tp;
+	bool							isnull;
+
+	tp = SearchSysCache(FOREIGNDATAWRAPPEROID,
+						ObjectIdGetDatum(fdwid),
+						0, 0, 0);
+
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+
+	fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
+
+	fdw = palloc(sizeof(ForeignDataWrapper));
+	fdw->fdwid = fdwid;
+	fdw->owner = fdwform->fdwowner;
+	fdw->fdwname = pstrdup(NameStr(fdwform->fdwname));
+
+	/* Extract library name */
+	datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
+							tp,
+							Anum_pg_foreign_data_wrapper_fdwlibrary,
+							&isnull);
+	fdw->fdwlibrary = pstrdup(TextDatumGetCString(datum));
+
+	fdw->lib = GetForeignDataWrapperLibrary(fdw->fdwlibrary);
+
+	/* Extract the options */
+	datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
+							tp,
+							Anum_pg_foreign_data_wrapper_fdwoptions,
+							&isnull);
+	fdw->options = untransformRelOptions(datum);
+
+	ReleaseSysCache(tp);
+
+	return fdw;
+}
+
+
+/*
+ * GetForeignDataWrapperOidByName - look up the foreign-data wrapper
+ * OID by name.
+ */
+Oid
+GetForeignDataWrapperOidByName(const char *fdwname, bool missing_ok)
+{
+	Oid fdwId;
+
+	fdwId = GetSysCacheOid(FOREIGNDATAWRAPPERNAME,
+						   CStringGetDatum(fdwname),
+						   0, 0, 0);
+
+	if (!OidIsValid(fdwId) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("foreign-data wrapper \"%s\" does not exist", fdwname)));
+
+	return fdwId;
+}
+
+
+/*
+ * GetForeignDataWrapperByName - look up the foreign-data wrapper
+ * definition by name.
+ */
+ForeignDataWrapper *
+GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
+{
+	Oid	fdwId = GetForeignDataWrapperOidByName(fdwname, missing_ok);
+
+	if (!OidIsValid(fdwId) && missing_ok)
+		return NULL;
+
+	return GetForeignDataWrapper(fdwId);
+}
+
+
+/*
+ * GetForeignServer - look up the foreign server definition.
+ */
+ForeignServer *
+GetForeignServer(Oid serverid)
+{
+	Form_pg_foreign_server	serverform;
+	ForeignServer		   *server;
+	HeapTuple 				tp;
+	Datum					datum;
+	bool 					isnull;
+
+	tp = SearchSysCache(FOREIGNSERVEROID,
+						ObjectIdGetDatum(serverid),
+						0, 0, 0);
+
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+
+	serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
+
+	server = palloc(sizeof(ForeignServer));
+	server->serverid = serverid;
+	server->servername = pstrdup(NameStr(serverform->srvname));
+	server->owner = serverform->srvowner;
+	server->fdwid = serverform->srvfdw;
+
+	/* Extract server type */
+	datum = SysCacheGetAttr(FOREIGNSERVEROID,
+							tp,
+							Anum_pg_foreign_server_srvtype,
+							&isnull);
+	server->servertype = isnull ? NULL : pstrdup(TextDatumGetCString(datum));
+
+	/* Extract server version */
+	datum = SysCacheGetAttr(FOREIGNSERVEROID,
+							tp,
+							Anum_pg_foreign_server_srvversion,
+							&isnull);
+	server->serverversion = isnull ? NULL : pstrdup(TextDatumGetCString(datum));
+
+	/* Extract the srvoptions */
+	datum = SysCacheGetAttr(FOREIGNSERVEROID,
+							tp,
+							Anum_pg_foreign_server_srvoptions,
+							&isnull);
+
+	/* untransformRelOptions does exactly what we want - avoid duplication */
+	server->options = untransformRelOptions(datum);
+
+	ReleaseSysCache(tp);
+
+	return server;
+}
+
+
+/*
+ * GetForeignServerByName - look up the foreign server oid by name.
+ */
+Oid
+GetForeignServerOidByName(const char *srvname, bool missing_ok)
+{
+	Oid	serverid;
+
+	serverid = GetSysCacheOid(FOREIGNSERVERNAME,
+							  CStringGetDatum(srvname),
+							  0, 0, 0);
+
+	if (!OidIsValid(serverid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("server \"%s\" does not exist", srvname)));
+
+	return serverid;
+}
+
+
+/*
+ * GetForeignServerByName - look up the foreign server definition by name.
+ */
+ForeignServer *
+GetForeignServerByName(const char *srvname, bool missing_ok)
+{
+    Oid	serverid = GetForeignServerOidByName(srvname, missing_ok);
+
+	if (!OidIsValid(serverid) && missing_ok)
+		return NULL;
+
+	return GetForeignServer(serverid);
+}
+
+
+/*
+ * GetUserMapping - look up the user mapping.
+ *
+ * If no mapping is found for the supplied user, we also look for
+ * PUBLIC mappings (userid == InvalidOid).
+ */
+UserMapping *
+GetUserMapping(Oid userid, Oid serverid)
+{
+	Form_pg_user_mapping	umform;
+	Datum		datum;
+	HeapTuple 	tp;
+	bool		isnull;
+	UserMapping	*um;
+
+	tp = SearchSysCache(USERMAPPINGUSERSERVER,
+						ObjectIdGetDatum(userid),
+						ObjectIdGetDatum(serverid),
+						0, 0);
+
+	if (!HeapTupleIsValid(tp))
+	{
+		/* Not found for the specific user -- try PUBLIC */
+		tp = SearchSysCache(USERMAPPINGUSERSERVER,
+							ObjectIdGetDatum(InvalidOid),
+							ObjectIdGetDatum(serverid),
+							0, 0);
+	}
+
+	if (!HeapTupleIsValid(tp))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("user mapping not found for \"%s\"",
+				MappingUserName(userid))));
+
+	umform = (Form_pg_user_mapping) GETSTRUCT(tp);
+
+	/* Extract the umoptions */
+	datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
+							tp,
+							Anum_pg_user_mapping_umoptions,
+							&isnull);
+
+	um = palloc(sizeof(UserMapping));
+	um->userid = userid;
+	um->serverid = serverid;
+	um->options = untransformRelOptions(datum);
+
+	ReleaseSysCache(tp);
+
+	return um;
+}
+
+
+/*
+ * deflist_to_tuplestore - Helper function to convert DefElem list to
+ * tuplestore usable in SRF.
+ */
+static void
+deflist_to_tuplestore(ReturnSetInfo *rsinfo, List *options)
+{
+	ListCell   *cell;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	Datum		values[2];
+	bool		nulls[2] = { 0 };
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	/*
+	 * Now prepare the result set.
+	 */
+	tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	foreach (cell, options)
+	{
+		DefElem	   *def = lfirst(cell);
+
+		values[0] = CStringGetTextDatum(def->defname);
+		values[1] = CStringGetTextDatum(((Value *)def->arg)->val.str);
+		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	MemoryContextSwitchTo(oldcontext);
+}
+
+
+/*
+ * Convert options array to name/value table.  Useful for information
+ * schema and pg_dump.
+ */
+Datum
+pg_options_to_table(PG_FUNCTION_ARGS)
+{
+	Datum array = PG_GETARG_DATUM(0);
+
+	deflist_to_tuplestore((ReturnSetInfo *) fcinfo->resultinfo, untransformRelOptions(array));
+
+	return (Datum) 0;
+}
diff --git a/src/backend/foreign/postgresql/Makefile b/src/backend/foreign/postgresql/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..40ed90f8d7090f8a234ace8ab139e5e4ffccf424
--- /dev/null
+++ b/src/backend/foreign/postgresql/Makefile
@@ -0,0 +1,27 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for postgresql foreign-data wrapper
+#
+# IDENTIFICATION
+#    $PostgreSQL: pgsql/src/backend/foreign/postgresql/Makefile,v 1.1 2008/12/19 16:25:17 petere Exp $
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/foreign/postgresql
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+NAME = postgresql_fdw
+OBJS = postgresql_fdw.o
+
+include $(top_srcdir)/src/Makefile.shlib
+
+all: all-shared-lib
+
+install: all install-lib
+
+installdirs: installdirs-lib
+
+clean distclean maintainer-clean: clean-lib
+	rm -f $(OBJS)
diff --git a/src/backend/foreign/postgresql/postgresql_fdw.c b/src/backend/foreign/postgresql/postgresql_fdw.c
new file mode 100644
index 0000000000000000000000000000000000000000..f1c881e397662a10ca5c1ecf9b6d5def42bd721b
--- /dev/null
+++ b/src/backend/foreign/postgresql/postgresql_fdw.c
@@ -0,0 +1,123 @@
+/*-------------------------------------------------------------------------
+ *
+ * postgresql_fdw.c
+ *        foreign-data wrapper for postgresql (libpq) connections.
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *        $PostgreSQL: pgsql/src/backend/foreign/postgresql/postgresql_fdw.c,v 1.1 2008/12/19 16:25:17 petere Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "lib/stringinfo.h"
+#include "nodes/value.h"
+#include "nodes/parsenodes.h"
+#include "nodes/makefuncs.h"
+#include "foreign/foreign.h"
+
+PG_MODULE_MAGIC;
+
+
+/*
+ * Describes the valid options for postgresql FDW, server and user mapping.
+ */
+typedef struct ConnectionOptions {
+	const char		   *optname;		/* Option name */
+	GenericOptionFlags	optflags;		/* Option usage bitmap */
+} ConnectionOptions;
+
+/*
+ * Copied from fe-connect.c PQconninfoOptions.
+ *
+ * The list is small - don't bother with bsearch if it stays so.
+ */
+static ConnectionOptions libpq_conninfo_options[] = {
+	{ "authtype",			ServerOpt		},
+	{ "service",			ServerOpt		},
+	{ "user",				UserMappingOpt	},
+	{ "password",			UserMappingOpt	},
+	{ "connect_timeout", 	ServerOpt		},
+	{ "dbname",				ServerOpt		},
+	{ "host",				ServerOpt		},
+	{ "hostaddr",			ServerOpt		},
+	{ "port",				ServerOpt		},
+	{ "tty",				ServerOpt		},
+	{ "options",			ServerOpt		},
+	{ "requiressl",			ServerOpt		},
+	{ "sslmode",			ServerOpt		},
+	{ "gsslib",				ServerOpt		},
+	{ NULL,					InvalidOpt		}
+};
+
+void _PG_fini(void);
+
+
+/*
+ * Check if the provided option is one of libpq conninfo options.
+ * We look at only options with matching flags.
+ */
+static bool
+is_conninfo_option(const char *option, GenericOptionFlags flags)
+{
+	ConnectionOptions *opt;
+
+	for (opt = libpq_conninfo_options; opt->optname != NULL; opt++)
+		if (flags & opt->optflags && strcmp(opt->optname, option) == 0)
+			return true;
+	return false;
+}
+
+/*
+ * Validate the generic option given to SERVER or USER MAPPING.
+ * Raise an ERROR if the option or its value is considered
+ * invalid.
+ *
+ * Valid server options are all libpq conninfo options except
+ * user and password -- these may only appear in USER MAPPING options.
+ */
+void
+_pg_validateOptionList(ForeignDataWrapper *fdw, GenericOptionFlags flags,
+				   List *options)
+{
+	ListCell *cell;
+
+	foreach (cell, options)
+	{
+		DefElem    *def = lfirst(cell);
+
+		if (!is_conninfo_option(def->defname, flags))
+		{
+			ConnectionOptions  *opt;
+			StringInfoData		buf;
+			const char		   *objtype;
+
+			/*
+			 * Unknown option specified, complain about it. Provide a hint
+			 * with list of valid options for the object.
+			 */
+			initStringInfo(&buf);
+			for (opt = libpq_conninfo_options; opt->optname != NULL; opt++)
+				if (flags & opt->optflags)
+					appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
+									 opt->optname);
+
+			if (flags & ServerOpt)
+				objtype = "server";
+			else if (flags & UserMappingOpt)
+				objtype = "user mapping";
+			else if (flags & FdwOpt)
+				objtype = "foreign-data wrapper";
+			else
+				objtype = "???";
+
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("invalid option \"%s\" to %s", def->defname, objtype),
+					 errhint("valid %s options are: %s", objtype, buf.data)));
+		}
+	}
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6a0c3374ce8703ee223b71d063e3f59b6db38009..86f555a03a617566e7c5aa1dcd24694b54a7ee44 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.415 2008/12/04 17:51:26 petere Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.416 2008/12/19 16:25:17 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2033,6 +2033,17 @@ _copyDefElem(DefElem *from)
 	return newnode;
 }
 
+static OptionDefElem *
+_copyOptionDefElem(OptionDefElem *from)
+{
+	OptionDefElem    *newnode = makeNode(OptionDefElem);
+
+	COPY_SCALAR_FIELD(alter_op);
+	COPY_NODE_FIELD(def);
+
+	return newnode;
+}
+
 static LockingClause *
 _copyLockingClause(LockingClause *from)
 {
@@ -2869,6 +2880,117 @@ _copyDropTableSpaceStmt(DropTableSpaceStmt *from)
 	return newnode;
 }
 
+static CreateFdwStmt *
+_copyCreateFdwStmt(CreateFdwStmt *from)
+{
+	CreateFdwStmt *newnode = makeNode(CreateFdwStmt);
+
+	COPY_STRING_FIELD(fdwname);
+	COPY_STRING_FIELD(library);
+	COPY_NODE_FIELD(options);
+
+	return newnode;
+}
+
+static AlterFdwStmt *
+_copyAlterFdwStmt(AlterFdwStmt *from)
+{
+	AlterFdwStmt *newnode = makeNode(AlterFdwStmt);
+
+	COPY_STRING_FIELD(fdwname);
+	COPY_STRING_FIELD(library);
+	COPY_NODE_FIELD(options);
+
+	return newnode;
+}
+
+static DropFdwStmt *
+_copyDropFdwStmt(DropFdwStmt *from)
+{
+	DropFdwStmt *newnode = makeNode(DropFdwStmt);
+
+	COPY_STRING_FIELD(fdwname);
+	COPY_SCALAR_FIELD(missing_ok);
+	COPY_SCALAR_FIELD(behavior);
+
+	return newnode;
+}
+
+static CreateForeignServerStmt *
+_copyCreateForeignServerStmt(CreateForeignServerStmt *from)
+{
+	CreateForeignServerStmt *newnode = makeNode(CreateForeignServerStmt);
+
+	COPY_STRING_FIELD(servername);
+	COPY_STRING_FIELD(servertype);
+	COPY_STRING_FIELD(version);
+	COPY_STRING_FIELD(fdwname);
+	COPY_NODE_FIELD(options);
+
+	return newnode;
+}
+
+static AlterForeignServerStmt *
+_copyAlterForeignServerStmt(AlterForeignServerStmt *from)
+{
+	AlterForeignServerStmt *newnode = makeNode(AlterForeignServerStmt);
+
+	COPY_STRING_FIELD(servername);
+	COPY_STRING_FIELD(version);
+	COPY_NODE_FIELD(options);
+	COPY_SCALAR_FIELD(has_version);
+
+	return newnode;
+}
+
+static DropForeignServerStmt *
+_copyDropForeignServerStmt(DropForeignServerStmt *from)
+{
+	DropForeignServerStmt *newnode = makeNode(DropForeignServerStmt);
+
+	COPY_STRING_FIELD(servername);
+	COPY_SCALAR_FIELD(missing_ok);
+	COPY_SCALAR_FIELD(behavior);
+
+	return newnode;
+}
+
+static CreateUserMappingStmt *
+_copyCreateUserMappingStmt(CreateUserMappingStmt *from)
+{
+	CreateUserMappingStmt *newnode = makeNode(CreateUserMappingStmt);
+
+	COPY_STRING_FIELD(username);
+	COPY_STRING_FIELD(servername);
+	COPY_NODE_FIELD(options);
+
+	return newnode;
+}
+
+static AlterUserMappingStmt *
+_copyAlterUserMappingStmt(AlterUserMappingStmt *from)
+{
+	AlterUserMappingStmt *newnode = makeNode(AlterUserMappingStmt);
+
+	COPY_STRING_FIELD(username);
+	COPY_STRING_FIELD(servername);
+	COPY_NODE_FIELD(options);
+
+	return newnode;
+}
+
+static DropUserMappingStmt *
+_copyDropUserMappingStmt(DropUserMappingStmt *from)
+{
+	DropUserMappingStmt *newnode = makeNode(DropUserMappingStmt);
+
+	COPY_STRING_FIELD(username);
+	COPY_STRING_FIELD(servername);
+	COPY_SCALAR_FIELD(missing_ok);
+
+	return newnode;
+}
+
 static CreateTrigStmt *
 _copyCreateTrigStmt(CreateTrigStmt *from)
 {
@@ -3696,6 +3818,33 @@ copyObject(void *from)
 		case T_DropTableSpaceStmt:
 			retval = _copyDropTableSpaceStmt(from);
 			break;
+		case T_CreateFdwStmt:
+			retval = _copyCreateFdwStmt(from);
+			break;
+		case T_AlterFdwStmt:
+			retval = _copyAlterFdwStmt(from);
+			break;
+		case T_DropFdwStmt:
+			retval = _copyDropFdwStmt(from);
+			break;
+		case T_CreateForeignServerStmt:
+			retval = _copyCreateForeignServerStmt(from);
+			break;
+		case T_AlterForeignServerStmt:
+			retval = _copyAlterForeignServerStmt(from);
+			break;
+		case T_DropForeignServerStmt:
+			retval = _copyDropForeignServerStmt(from);
+			break;
+		case T_CreateUserMappingStmt:
+			retval = _copyCreateUserMappingStmt(from);
+			break;
+		case T_AlterUserMappingStmt:
+			retval = _copyAlterUserMappingStmt(from);
+			break;
+		case T_DropUserMappingStmt:
+			retval = _copyDropUserMappingStmt(from);
+			break;
 		case T_CreateTrigStmt:
 			retval = _copyCreateTrigStmt(from);
 			break;
@@ -3823,6 +3972,9 @@ copyObject(void *from)
 		case T_DefElem:
 			retval = _copyDefElem(from);
 			break;
+		case T_OptionDefElem:
+			retval = _copyOptionDefElem(from);
+			break;
 		case T_LockingClause:
 			retval = _copyLockingClause(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index ebf8cea2f0d80f35f5a6764959f479f1869dabbc..e5e2bc4422644e1052dd2fd19328465113c4e7ff 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -22,7 +22,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.340 2008/12/04 17:51:26 petere Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.341 2008/12/19 16:25:17 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1518,6 +1518,99 @@ _equalDropTableSpaceStmt(DropTableSpaceStmt *a, DropTableSpaceStmt *b)
 	return true;
 }
 
+static bool
+_equalCreateFdwStmt(CreateFdwStmt *a, CreateFdwStmt *b)
+{
+	COMPARE_STRING_FIELD(fdwname);
+	COMPARE_STRING_FIELD(library);
+	COMPARE_NODE_FIELD(options);
+
+	return true;
+}
+
+static bool
+_equalAlterFdwStmt(AlterFdwStmt *a, AlterFdwStmt *b)
+{
+	COMPARE_STRING_FIELD(fdwname);
+	COMPARE_STRING_FIELD(library);
+	COMPARE_NODE_FIELD(options);
+
+	return true;
+}
+
+static bool
+_equalDropFdwStmt(DropFdwStmt *a, DropFdwStmt *b)
+{
+	COMPARE_STRING_FIELD(fdwname);
+	COMPARE_SCALAR_FIELD(missing_ok);
+	COMPARE_SCALAR_FIELD(behavior);
+
+	return true;
+}
+
+static bool
+_equalCreateForeignServerStmt(CreateForeignServerStmt *a, CreateForeignServerStmt *b)
+{
+	COMPARE_STRING_FIELD(servername);
+	COMPARE_STRING_FIELD(servertype);
+	COMPARE_STRING_FIELD(version);
+	COMPARE_STRING_FIELD(fdwname);
+	COMPARE_NODE_FIELD(options);
+
+	return true;
+}
+
+static bool
+_equalAlterForeignServerStmt(AlterForeignServerStmt *a, AlterForeignServerStmt *b)
+{
+	COMPARE_STRING_FIELD(servername);
+	COMPARE_STRING_FIELD(version);
+	COMPARE_NODE_FIELD(options);
+	COMPARE_SCALAR_FIELD(has_version);
+
+	return true;
+}
+
+static bool
+_equalDropForeignServerStmt(DropForeignServerStmt *a, DropForeignServerStmt *b)
+{
+	COMPARE_STRING_FIELD(servername);
+	COMPARE_SCALAR_FIELD(missing_ok);
+	COMPARE_SCALAR_FIELD(behavior);
+
+	return true;
+}
+
+static bool
+_equalCreateUserMappingStmt(CreateUserMappingStmt *a, CreateUserMappingStmt *b)
+{
+	COMPARE_STRING_FIELD(username);
+	COMPARE_STRING_FIELD(servername);
+	COMPARE_NODE_FIELD(options);
+
+	return true;
+}
+
+static bool
+_equalAlterUserMappingStmt(AlterUserMappingStmt *a, AlterUserMappingStmt *b)
+{
+	COMPARE_STRING_FIELD(username);
+	COMPARE_STRING_FIELD(servername);
+	COMPARE_NODE_FIELD(options);
+
+	return true;
+}
+
+static bool
+_equalDropUserMappingStmt(DropUserMappingStmt *a, DropUserMappingStmt *b)
+{
+	COMPARE_STRING_FIELD(username);
+	COMPARE_STRING_FIELD(servername);
+	COMPARE_SCALAR_FIELD(missing_ok);
+
+	return true;
+}
+
 static bool
 _equalCreateTrigStmt(CreateTrigStmt *a, CreateTrigStmt *b)
 {
@@ -1956,6 +2049,15 @@ _equalDefElem(DefElem *a, DefElem *b)
 	return true;
 }
 
+static bool
+_equalOptionDefElem(OptionDefElem *a, OptionDefElem *b)
+{
+	COMPARE_SCALAR_FIELD(alter_op);
+	COMPARE_NODE_FIELD(def);
+
+	return true;
+}
+
 static bool
 _equalLockingClause(LockingClause *a, LockingClause *b)
 {
@@ -2534,6 +2636,33 @@ equal(void *a, void *b)
 		case T_DropTableSpaceStmt:
 			retval = _equalDropTableSpaceStmt(a, b);
 			break;
+		case T_CreateFdwStmt:
+			retval = _equalCreateFdwStmt(a, b);
+			break;
+		case T_AlterFdwStmt:
+			retval = _equalAlterFdwStmt(a, b);
+			break;
+		case T_DropFdwStmt:
+			retval = _equalDropFdwStmt(a, b);
+			break;
+		case T_CreateForeignServerStmt:
+			retval = _equalCreateForeignServerStmt(a, b);
+			break;
+		case T_AlterForeignServerStmt:
+			retval = _equalAlterForeignServerStmt(a, b);
+			break;
+		case T_DropForeignServerStmt:
+			retval = _equalDropForeignServerStmt(a, b);
+			break;
+		case T_CreateUserMappingStmt:
+			retval = _equalCreateUserMappingStmt(a, b);
+			break;
+		case T_AlterUserMappingStmt:
+			retval = _equalAlterUserMappingStmt(a, b);
+			break;
+		case T_DropUserMappingStmt:
+			retval = _equalDropUserMappingStmt(a, b);
+			break;
 		case T_CreateTrigStmt:
 			retval = _equalCreateTrigStmt(a, b);
 			break;
@@ -2661,6 +2790,9 @@ equal(void *a, void *b)
 		case T_DefElem:
 			retval = _equalDefElem(a, b);
 			break;
+		case T_OptionDefElem:
+			retval = _equalOptionDefElem(a, b);
+			break;
 		case T_LockingClause:
 			retval = _equalLockingClause(a, b);
 			break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 42539f6f97bfe0c023315c3c23ebc4ae6f598d96..cb4f09be5a1a56e2f3df04b3e1b85aaf97d9dd44 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/makefuncs.c,v 1.60 2008/09/01 20:42:44 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/makefuncs.c,v 1.61 2008/12/19 16:25:17 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -361,3 +361,16 @@ makeDefElem(char *name, Node *arg)
 	res->arg = arg;
 	return res;
 }
+
+/*
+ * makeOptionDefElem -
+ *	build an OptionDefElem node
+ */
+OptionDefElem *
+makeOptionDefElem(int op, DefElem *def)
+{
+	OptionDefElem *res = makeNode(OptionDefElem);
+	res->alter_op = op;
+	res->def = def;
+	return res;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 337af633272c72269ac2aba617d678b697cf7cca..cc81e6564610d3a194a796f5cb3c57f8f04d1875 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.645 2008/12/18 18:20:34 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.646 2008/12/19 16:25:17 petere Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -156,6 +156,7 @@ static TypeName *TableFuncTypeName(List *columns);
 	FunctionParameterMode fun_param_mode;
 	FuncWithArgs		*funwithargs;
 	DefElem				*defelt;
+	OptionDefElem		*optdef;
 	SortBy				*sortby;
 	JoinExpr			*jexpr;
 	IndexElem			*ielem;
@@ -172,19 +173,22 @@ static TypeName *TableFuncTypeName(List *columns);
 }
 
 %type <node>	stmt schema_stmt
-		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt
+		AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterFdwStmt
+		AlterForeignServerStmt AlterGroupStmt
 		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
-		AlterUserStmt AlterUserSetStmt AlterRoleStmt AlterRoleSetStmt
+		AlterUserStmt AlterUserMappingStmt AlterUserSetStmt AlterRoleStmt AlterRoleSetStmt
 		AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
 		ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
 		CreateDomainStmt CreateGroupStmt CreateOpClassStmt
 		CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
 		CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
-		CreateAssertStmt CreateTrigStmt CreateUserStmt CreateRoleStmt
+		CreateFdwStmt CreateForeignServerStmt CreateAssertStmt CreateTrigStmt
+		CreateUserStmt CreateUserMappingStmt CreateRoleStmt
 		CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt
 		DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
 		DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
-		DropUserStmt DropdbStmt DropTableSpaceStmt ExplainStmt FetchStmt
+		DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
+		DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
 		GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
 		LockStmt NotifyStmt ExplainableStmt PreparableStmt
 		CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
@@ -222,6 +226,10 @@ static TypeName *TableFuncTypeName(List *columns);
 %type <list>	OptRoleList
 %type <defelt>	OptRoleElem
 
+%type <str>		opt_type
+%type <str>		foreign_server_version opt_foreign_server_version
+%type <str>		auth_ident
+
 %type <str>		OptSchemaName
 %type <list>	OptSchemaEltList
 
@@ -274,6 +282,7 @@ static TypeName *TableFuncTypeName(List *columns);
 				prep_type_clause
 				execute_param_clause using_clause returning_clause
 				enum_val_list table_func_column_list
+				create_generic_options alter_generic_options
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target
@@ -342,6 +351,12 @@ static TypeName *TableFuncTypeName(List *columns);
 %type <range>	relation_expr_opt_alias
 %type <target>	target_el single_set_clause set_target insert_column_item
 
+%type <str>		generic_option_name
+%type <node>	generic_option_arg
+%type <defelt>	generic_option_elem
+%type <optdef>	alter_generic_option_elem
+%type <list>	generic_option_list alter_generic_option_list
+
 %type <typnam>	Typename SimpleTypename ConstTypename
 				GenericType Numeric opt_float
 				Character ConstCharacter
@@ -436,7 +451,7 @@ static TypeName *TableFuncTypeName(List *columns);
 	KEY
 
 	LANCOMPILER LANGUAGE LARGE_P LAST_P LEADING LEAST LEFT LEVEL
-	LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
+	LIBRARY LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
 	LOCK_P LOGIN_P
 
 	MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -445,7 +460,7 @@ static TypeName *TableFuncTypeName(List *columns);
 	NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
 	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC
 
-	OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OR
+	OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER OUT_P OUTER_P OVERLAPS OVERLAY OWNED OWNER
 
 	PARSER PARTIAL PASSWORD PLACING PLANS POSITION
@@ -459,7 +474,7 @@ static TypeName *TableFuncTypeName(List *columns);
 	REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
 
 	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
-	SERIALIZABLE SESSION SESSION_USER SET SETOF SHARE
+	SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
 	SHOW SIMILAR SIMPLE SMALLINT SOME STABLE STANDALONE_P START STATEMENT
 	STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING SUPERUSER_P
 	SYMMETRIC SYSID SYSTEM_P
@@ -474,7 +489,7 @@ static TypeName *TableFuncTypeName(List *columns);
 	VACUUM VALID VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
 	VERBOSE VERSION_P VIEW VOLATILE
 
-	WHEN WHERE WHITESPACE_P WITH WITHOUT WORK WRITE
+	WHEN WHERE WHITESPACE_P WITH WITHOUT WORK WRAPPER WRITE
 
 	XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLFOREST XMLPARSE
 	XMLPI XMLROOT XMLSERIALIZE
@@ -562,6 +577,8 @@ stmt :
 			AlterDatabaseStmt
 			| AlterDatabaseSetStmt
 			| AlterDomainStmt
+			| AlterFdwStmt
+			| AlterForeignServerStmt
 			| AlterFunctionStmt
 			| AlterGroupStmt
 			| AlterObjectSchemaStmt
@@ -572,6 +589,7 @@ stmt :
 			| AlterRoleStmt
 			| AlterTSConfigurationStmt
 			| AlterTSDictionaryStmt
+			| AlterUserMappingStmt
 			| AlterUserSetStmt
 			| AlterUserStmt
 			| AnalyzeStmt
@@ -586,6 +604,8 @@ stmt :
 			| CreateCastStmt
 			| CreateConversionStmt
 			| CreateDomainStmt
+			| CreateFdwStmt
+			| CreateForeignServerStmt
 			| CreateFunctionStmt
 			| CreateGroupStmt
 			| CreateOpClassStmt
@@ -599,6 +619,7 @@ stmt :
 			| CreateTrigStmt
 			| CreateRoleStmt
 			| CreateUserStmt
+			| CreateUserMappingStmt
 			| CreatedbStmt
 			| DeallocateStmt
 			| DeclareCursorStmt
@@ -607,6 +628,8 @@ stmt :
 			| DiscardStmt
 			| DropAssertStmt
 			| DropCastStmt
+			| DropFdwStmt
+			| DropForeignServerStmt
 			| DropGroupStmt
 			| DropOpClassStmt
 			| DropOpFamilyStmt
@@ -618,6 +641,7 @@ stmt :
 			| DropTrigStmt
 			| DropRoleStmt
 			| DropUserStmt
+			| DropUserMappingStmt
 			| DropdbStmt
 			| ExecuteStmt
 			| ExplainStmt
@@ -2715,6 +2739,313 @@ DropTableSpaceStmt: DROP TABLESPACE name
 				}
 		;
 
+/*****************************************************************************
+ *
+ * 		QUERY:
+ *             CREATE FOREIGN DATA WRAPPER name LIBRARY 'library_name' LANGUAGE C
+ *
+ *****************************************************************************/
+
+CreateFdwStmt: CREATE FOREIGN DATA_P WRAPPER name LIBRARY Sconst LANGUAGE ColId create_generic_options
+				{
+					CreateFdwStmt *n = makeNode(CreateFdwStmt);
+					n->fdwname = $5;
+					n->library = $7;
+					n->options = $10;
+					$$ = (Node *) n;
+
+					if (pg_strcasecmp($9, "C") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("language for foreign-data wrapper must be C"),
+								 scanner_errposition(@9)));
+				}
+		;
+
+/*****************************************************************************
+ *
+ * 		QUERY :
+ *				DROP FOREIGN DATA WRAPPER name
+ *
+ ****************************************************************************/
+
+DropFdwStmt: DROP FOREIGN DATA_P WRAPPER name opt_drop_behavior
+				{
+					DropFdwStmt *n = makeNode(DropFdwStmt);
+					n->fdwname = $5;
+					n->missing_ok = false;
+					n->behavior = $6;
+					$$ = (Node *) n;
+				}
+				|  DROP FOREIGN DATA_P WRAPPER IF_P EXISTS name opt_drop_behavior
+                {
+					DropFdwStmt *n = makeNode(DropFdwStmt);
+					n->fdwname = $7;
+					n->missing_ok = true;
+					n->behavior = $8;
+					$$ = (Node *) n;
+				}
+		;
+
+/*****************************************************************************
+ *
+ * 		QUERY :
+ *				ALTER FOREIGN DATA WRAPPER name
+ *
+ ****************************************************************************/
+
+AlterFdwStmt: ALTER FOREIGN DATA_P WRAPPER name LIBRARY Sconst alter_generic_options
+				{
+					AlterFdwStmt *n = makeNode(AlterFdwStmt);
+					n->fdwname = $5;
+					n->library = $7;
+					n->options = $8;
+					$$ = (Node *) n;
+				}
+			| ALTER FOREIGN DATA_P WRAPPER name LIBRARY Sconst
+				{
+					AlterFdwStmt *n = makeNode(AlterFdwStmt);
+					n->fdwname = $5;
+					n->library = $7;
+					$$ = (Node *) n;
+				}
+			| ALTER FOREIGN DATA_P WRAPPER name alter_generic_options
+				{
+					AlterFdwStmt *n = makeNode(AlterFdwStmt);
+					n->fdwname = $5;
+					n->options = $6;
+					$$ = (Node *) n;
+				}
+		;
+
+/* Options definition for CREATE FDW, SERVER and USER MAPPING */
+create_generic_options:
+				OPTIONS '(' generic_option_list ')'			{ $$ = $3; }
+				| /*EMPTY*/									{ $$ = NIL; }
+		;
+
+generic_option_list:		generic_option_elem
+					{
+						$$ = list_make1(makeOptionDefElem(ALTER_OPT_ADD, $1));
+					}
+				| generic_option_list ',' generic_option_elem
+					{
+						$$ = lappend($1, makeOptionDefElem(ALTER_OPT_ADD, $3));
+					}
+		;
+
+/* Options definition for ALTER FDW, SERVER and USER MAPPING */
+alter_generic_options:
+				OPTIONS	'(' alter_generic_option_list ')'	{ $$ = $3; }
+		;
+
+alter_generic_option_list:
+				alter_generic_option_elem
+					{
+						$$ = list_make1($1);
+					}
+				| generic_option_elem
+					{
+						$$ = list_make1(makeOptionDefElem(ALTER_OPT_ADD, $1));
+					}
+				| alter_generic_option_list ',' alter_generic_option_elem
+					{
+						$$ = lappend($1, $3);
+					}
+				| alter_generic_option_list ',' generic_option_elem
+					{
+						$$ = lappend($1, makeOptionDefElem(ALTER_OPT_ADD, $3));
+					}
+		;
+
+alter_generic_option_elem:
+				ADD_P generic_option_elem
+					{
+						$$ = makeOptionDefElem(ALTER_OPT_ADD, $2);
+					}
+				| SET generic_option_elem
+					{
+						$$ = makeOptionDefElem(ALTER_OPT_SET, $2);
+					}
+				| DROP generic_option_name
+					{
+						$$ = makeOptionDefElem(ALTER_OPT_DROP,
+											   makeDefElem($2, NULL));
+					}
+		;
+
+generic_option_elem:
+				generic_option_name generic_option_arg			{ $$ = makeDefElem($1, $2); }
+		;
+
+generic_option_name:
+				attr_name			{ $$ = $1; }
+		;
+
+generic_option_arg:
+				Sconst				{ $$ = (Node *)makeString($1); }
+		;
+
+/*****************************************************************************
+ *
+ * 		QUERY:
+ *             CREATE SERVER name [TYPE] [VERSION] [OPTIONS]
+ *
+ *****************************************************************************/
+
+CreateForeignServerStmt: CREATE SERVER name opt_type opt_foreign_server_version
+						 FOREIGN DATA_P WRAPPER name create_generic_options
+				{
+					CreateForeignServerStmt *n = makeNode(CreateForeignServerStmt);
+					n->servername = $3;
+					n->servertype = $4;
+					n->version = $5;
+					n->fdwname = $9;
+					n->options = $10;
+					$$ = (Node *) n;
+				}
+		;
+
+opt_type:
+			TYPE_P Sconst			{ $$ = $2; }
+			| /*EMPTY*/				{ $$ = NULL; }
+		;
+
+
+foreign_server_version:
+			VERSION_P Sconst		{ $$ = $2; }
+		|	VERSION_P NULL_P		{ $$ = NULL; }
+		;
+
+opt_foreign_server_version:
+			foreign_server_version 	{ $$ = $1; }
+			| /*EMPTY*/				{ $$ = NULL; }
+		;
+
+/*****************************************************************************
+ *
+ * 		QUERY :
+ *				DROP SERVER name
+ *
+ ****************************************************************************/
+
+DropForeignServerStmt: DROP SERVER name opt_drop_behavior
+				{
+					DropForeignServerStmt *n = makeNode(DropForeignServerStmt);
+					n->servername = $3;
+					n->missing_ok = false;
+					n->behavior = $4;
+					$$ = (Node *) n;
+				}
+				|  DROP SERVER IF_P EXISTS name opt_drop_behavior
+                {
+					DropForeignServerStmt *n = makeNode(DropForeignServerStmt);
+					n->servername = $5;
+					n->missing_ok = true;
+					n->behavior = $6;
+					$$ = (Node *) n;
+				}
+		;
+
+/*****************************************************************************
+ *
+ * 		QUERY :
+ *				ALTER SERVER name [VERSION] [OPTIONS]
+ *
+ ****************************************************************************/
+
+AlterForeignServerStmt: ALTER SERVER name foreign_server_version alter_generic_options
+				{
+					AlterForeignServerStmt *n = makeNode(AlterForeignServerStmt);
+					n->servername = $3;
+					n->version = $4;
+					n->options = $5;
+					n->has_version = true;
+					$$ = (Node *) n;
+				}
+			| ALTER SERVER name foreign_server_version
+				{
+					AlterForeignServerStmt *n = makeNode(AlterForeignServerStmt);
+					n->servername = $3;
+					n->version = $4;
+					n->has_version = true;
+					$$ = (Node *) n;
+				}
+			| ALTER SERVER name alter_generic_options
+				{
+					AlterForeignServerStmt *n = makeNode(AlterForeignServerStmt);
+					n->servername = $3;
+					n->options = $4;
+					$$ = (Node *) n;
+				}
+		;
+
+/*****************************************************************************
+ *
+ * 		QUERY:
+ *             CREATE USER MAPPING FOR auth_ident SERVER name [OPTIONS]
+ *
+ *****************************************************************************/
+
+CreateUserMappingStmt: CREATE USER MAPPING FOR auth_ident SERVER name create_generic_options
+				{
+					CreateUserMappingStmt *n = makeNode(CreateUserMappingStmt);
+					n->username = $5;
+					n->servername = $7;
+					n->options = $8;
+					$$ = (Node *) n;
+				}
+		;
+
+/* User mapping authorization identifier */
+auth_ident:
+			CURRENT_USER 	{ $$ = "current_user"; }
+		|	USER			{ $$ = "current_user"; }
+		|	RoleId 			{ $$ = (strcmp($1, "public") == 0) ? NULL : $1 }
+		;
+
+/*****************************************************************************
+ *
+ * 		QUERY :
+ *				DROP USER MAPPING FOR auth_ident SERVER name
+ *
+ ****************************************************************************/
+
+DropUserMappingStmt: DROP USER MAPPING FOR auth_ident SERVER name
+				{
+					DropUserMappingStmt *n = makeNode(DropUserMappingStmt);
+					n->username = $5;
+					n->servername = $7;
+					n->missing_ok = false;
+					$$ = (Node *) n;
+				}
+				|  DROP USER MAPPING IF_P EXISTS FOR auth_ident SERVER name
+                {
+					DropUserMappingStmt *n = makeNode(DropUserMappingStmt);
+					n->username = $7;
+					n->servername = $9;
+					n->missing_ok = true;
+					$$ = (Node *) n;
+				}
+		;
+
+/*****************************************************************************
+ *
+ * 		QUERY :
+ *				ALTER USER MAPPING FOR auth_ident SERVER name OPTIONS
+ *
+ ****************************************************************************/
+
+AlterUserMappingStmt: ALTER USER MAPPING FOR auth_ident SERVER name alter_generic_options
+				{
+					AlterUserMappingStmt *n = makeNode(AlterUserMappingStmt);
+					n->username = $5;
+					n->servername = $7;
+					n->options = $8;
+					$$ = (Node *) n;
+				}
+		;
+
 /*****************************************************************************
  *
  *		QUERIES :
@@ -3912,6 +4243,20 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
+			| FOREIGN DATA_P WRAPPER name_list
+				{
+					PrivTarget *n = makeNode(PrivTarget);
+					n->objtype = ACL_OBJECT_FDW;
+					n->objs = $4;
+					$$ = n;
+				}
+			| FOREIGN SERVER name_list
+				{
+					PrivTarget *n = makeNode(PrivTarget);
+					n->objtype = ACL_OBJECT_FOREIGN_SERVER;
+					n->objs = $3;
+					$$ = n;
+				}
 			| FUNCTION function_with_argtypes_list
 				{
 					PrivTarget *n = makeNode(PrivTarget);
@@ -5123,6 +5468,22 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
 					n->newowner = $8;
 					$$ = (Node *)n;
 				}
+			| ALTER FOREIGN DATA_P WRAPPER name OWNER TO RoleId
+				{
+					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+					n->objectType = OBJECT_FDW;
+					n->object = list_make1(makeString($5));
+					n->newowner = $8;
+					$$ = (Node *)n;
+				}
+			| ALTER SERVER name OWNER TO RoleId
+				{
+					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+					n->objectType = OBJECT_FOREIGN_SERVER;
+					n->object = list_make1(makeString($3));
+					n->newowner = $6;
+					$$ = (Node *)n;
+				}
 		;
 
 
@@ -9556,6 +9917,7 @@ unreserved_keyword:
 			| INVOKER
 			| ISOLATION
 			| KEY
+			| LIBRARY
 			| LANCOMPILER
 			| LANGUAGE
 			| LARGE_P
@@ -9594,6 +9956,7 @@ unreserved_keyword:
 			| OIDS
 			| OPERATOR
 			| OPTION
+			| OPTIONS
 			| OWNED
 			| OWNER
 			| PARSER
@@ -9629,6 +9992,7 @@ unreserved_keyword:
 			| ROWS
 			| RULE
 			| SAVEPOINT
+			| SERVER
 			| SCHEMA
 			| SCROLL
 			| SEARCH
@@ -9681,6 +10045,7 @@ unreserved_keyword:
 			| WHITESPACE_P
 			| WITHOUT
 			| WORK
+			| WRAPPER
 			| WRITE
 			| XML_P
 			| YEAR_P
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index 9eb6d0822693bad5dba79c9e1139cb4cd74bd86a..bf7b1f6ad2e710dd480b91428b099103549e24c8 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.205 2008/10/27 09:37:47 petere Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.206 2008/12/19 16:25:17 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -229,6 +229,7 @@ const ScanKeyword ScanKeywords[] = {
 	{"least", LEAST, COL_NAME_KEYWORD},
 	{"left", LEFT, TYPE_FUNC_NAME_KEYWORD},
 	{"level", LEVEL, UNRESERVED_KEYWORD},
+	{"library", LIBRARY, UNRESERVED_KEYWORD},
 	{"like", LIKE, TYPE_FUNC_NAME_KEYWORD},
 	{"limit", LIMIT, RESERVED_KEYWORD},
 	{"listen", LISTEN, UNRESERVED_KEYWORD},
@@ -281,6 +282,7 @@ const ScanKeyword ScanKeywords[] = {
 	{"only", ONLY, RESERVED_KEYWORD},
 	{"operator", OPERATOR, UNRESERVED_KEYWORD},
 	{"option", OPTION, UNRESERVED_KEYWORD},
+	{"options", OPTIONS, UNRESERVED_KEYWORD},
 	{"or", OR, RESERVED_KEYWORD},
 	{"order", ORDER, RESERVED_KEYWORD},
 	{"out", OUT_P, COL_NAME_KEYWORD},
@@ -339,6 +341,7 @@ const ScanKeyword ScanKeywords[] = {
 	{"select", SELECT, RESERVED_KEYWORD},
 	{"sequence", SEQUENCE, UNRESERVED_KEYWORD},
 	{"serializable", SERIALIZABLE, UNRESERVED_KEYWORD},
+	{"server", SERVER, UNRESERVED_KEYWORD},
 	{"session", SESSION, UNRESERVED_KEYWORD},
 	{"session_user", SESSION_USER, RESERVED_KEYWORD},
 	{"set", SET, UNRESERVED_KEYWORD},
@@ -411,6 +414,7 @@ const ScanKeyword ScanKeywords[] = {
 	{"with", WITH, RESERVED_KEYWORD},
 	{"without", WITHOUT, UNRESERVED_KEYWORD},
 	{"work", WORK, UNRESERVED_KEYWORD},
+	{"wrapper", WRAPPER, UNRESERVED_KEYWORD},
 	{"write", WRITE, UNRESERVED_KEYWORD},
 	{"xml", XML_P, UNRESERVED_KEYWORD},
 	{"xmlattributes", XMLATTRIBUTES, COL_NAME_KEYWORD},
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f222b9cd75e0656420983be744edc98be1cc6853..555e8730dd9aefc888381f52551ce94688a4b132 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.302 2008/12/04 17:51:26 petere Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.303 2008/12/19 16:25:17 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -203,6 +203,15 @@ check_xact_readonly(Node *parsetree)
 		case T_ReassignOwnedStmt:
 		case T_AlterTSDictionaryStmt:
 		case T_AlterTSConfigurationStmt:
+		case T_CreateFdwStmt:
+		case T_AlterFdwStmt:
+		case T_DropFdwStmt:
+		case T_CreateForeignServerStmt:
+		case T_AlterForeignServerStmt:
+		case T_DropForeignServerStmt:
+		case T_CreateUserMappingStmt:
+		case T_AlterUserMappingStmt:
+		case T_DropUserMappingStmt:
 			ereport(ERROR,
 					(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
 					 errmsg("transaction is read-only")));
@@ -452,6 +461,42 @@ ProcessUtility(Node *parsetree,
 			DropTableSpace((DropTableSpaceStmt *) parsetree);
 			break;
 
+		case T_CreateFdwStmt:
+			CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+			break;
+
+		case T_AlterFdwStmt:
+			AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+			break;
+
+		case T_DropFdwStmt:
+			RemoveForeignDataWrapper((DropFdwStmt *) parsetree);
+			break;
+
+		case T_CreateForeignServerStmt:
+			CreateForeignServer((CreateForeignServerStmt *) parsetree);
+			break;
+
+		case T_AlterForeignServerStmt:
+			AlterForeignServer((AlterForeignServerStmt *) parsetree);
+			break;
+
+		case T_DropForeignServerStmt:
+			RemoveForeignServer((DropForeignServerStmt *) parsetree);
+			break;
+
+		case T_CreateUserMappingStmt:
+			CreateUserMapping((CreateUserMappingStmt *) parsetree);
+			break;
+
+		case T_AlterUserMappingStmt:
+			AlterUserMapping((AlterUserMappingStmt *) parsetree);
+			break;
+
+		case T_DropUserMappingStmt:
+			RemoveUserMapping((DropUserMappingStmt *) parsetree);
+			break;
+
 		case T_DropStmt:
 			{
 				DropStmt   *stmt = (DropStmt *) parsetree;
@@ -1310,6 +1355,42 @@ CreateCommandTag(Node *parsetree)
 			tag = "DROP TABLESPACE";
 			break;
 
+		case T_CreateFdwStmt:
+			tag = "CREATE FOREIGN DATA WRAPPER";
+			break;
+
+		case T_AlterFdwStmt:
+			tag = "ALTER FOREIGN DATA WRAPPER";
+			break;
+
+		case T_DropFdwStmt:
+			tag = "DROP FOREIGN DATA WRAPPER";
+			break;
+
+		case T_CreateForeignServerStmt:
+			tag = "CREATE SERVER";
+			break;
+
+		case T_AlterForeignServerStmt:
+			tag = "ALTER SERVER";
+			break;
+
+		case T_DropForeignServerStmt:
+			tag = "DROP SERVER";
+			break;
+
+		case T_CreateUserMappingStmt:
+			tag = "CREATE USER MAPPING";
+			break;
+
+		case T_AlterUserMappingStmt:
+			tag = "ALTER USER MAPPING";
+			break;
+
+		case T_DropUserMappingStmt:
+			tag = "DROP USER MAPPING";
+			break;
+
 		case T_DropStmt:
 			switch (((DropStmt *) parsetree)->removeType)
 			{
@@ -1523,6 +1604,12 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_TSDICTIONARY:
 					tag = "ALTER TEXT SEARCH DICTIONARY";
 					break;
+				case OBJECT_FDW:
+					tag = "ALTER FOREIGN DATA WRAPPER";
+					break;
+				case OBJECT_FOREIGN_SERVER:
+					tag = "ALTER SERVER";
+					break;
 				default:
 					tag = "???";
 					break;
@@ -2037,6 +2124,18 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreateFdwStmt:
+		case T_AlterFdwStmt:
+		case T_DropFdwStmt:
+		case T_CreateForeignServerStmt:
+		case T_AlterForeignServerStmt:
+		case T_DropForeignServerStmt:
+		case T_CreateUserMappingStmt:
+		case T_AlterUserMappingStmt:
+		case T_DropUserMappingStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 		case T_DropStmt:
 			lev = LOGSTMT_DDL;
 			break;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 963112ef74a369cb388d0b4388bc0472b6d47d7d..4641aecd59f2d6936839c7c93e0b9258afe532f3 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.143 2008/12/15 18:09:41 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.144 2008/12/19 16:25:17 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,6 +22,7 @@
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/tablespace.h"
+#include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -577,6 +578,14 @@ acldefault(GrantObjectType objtype, Oid ownerId)
 			world_default = ACL_NO_RIGHTS;
 			owner_default = ACL_ALL_RIGHTS_TABLESPACE;
 			break;
+		case ACL_OBJECT_FDW:
+			world_default = ACL_NO_RIGHTS;
+			owner_default = ACL_ALL_RIGHTS_FDW;
+			break;
+		case ACL_OBJECT_FOREIGN_SERVER:
+			world_default = ACL_NO_RIGHTS;
+			owner_default = ACL_ALL_RIGHTS_FOREIGN_SERVER;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			world_default = ACL_NO_RIGHTS;		/* keep compiler quiet */
@@ -1823,6 +1832,156 @@ convert_database_priv_string(text *priv_type_text)
 }
 
 
+/*
+ * has_foreign_data_wrapper_privilege variants
+ *		These are all named "has_foreign_data_wrapper_privilege" at the SQL level.
+ *		They take various combinations of foreign-data wrapper name,
+ *		fdw OID, user name, user OID, or implicit user = current_user.
+ *
+ *		The result is a boolean value: true if user has the indicated
+ *		privilege, false if not.  The variants that take an OID return
+ *		NULL if the OID doesn't exist.
+ */
+
+/*
+ * has_foreign_data_wrapper_privilege
+ *		Check user privileges on a foreign-data wrapper.
+ */
+static Datum
+has_foreign_data_wrapper_privilege(Oid roleid, Oid fdwid, text *priv_type_text)
+{
+	AclResult	aclresult;
+	AclMode		mode = ACL_NO_RIGHTS;
+	char	   *priv_type = text_to_cstring(priv_type_text);
+
+	if (pg_strcasecmp(priv_type, "USAGE") == 0)
+		mode = ACL_USAGE;
+	else if (pg_strcasecmp(priv_type, "USAGE WITH GRANT OPTION") == 0)
+		mode = ACL_GRANT_OPTION_FOR(ACL_USAGE);
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized privilege type: \"%s\"", priv_type)));
+
+	aclresult = pg_foreign_data_wrapper_aclcheck(fdwid, roleid, mode);
+
+	PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_foreign_data_wrapper_privilege_name_name
+ *		Check user privileges on a foreign-data wrapper given
+ *		name username, text fdwname, and text priv name.
+ */
+Datum
+has_foreign_data_wrapper_privilege_name_name(PG_FUNCTION_ARGS)
+{
+	Name		username = PG_GETARG_NAME(0);
+	char	   *fdwname = text_to_cstring(PG_GETARG_TEXT_P(1));
+	text	   *priv_type_text = PG_GETARG_TEXT_P(2);
+
+	return has_foreign_data_wrapper_privilege(get_roleid_checked(NameStr(*username)),
+											  GetForeignDataWrapperOidByName(fdwname, false),
+											  priv_type_text);
+}
+
+/*
+ * has_foreign_data_wrapper_privilege_name
+ *		Check user privileges on a foreign-data wrapper given
+ *		text fdwname and text priv name.
+ *		current_user is assumed
+ */
+Datum
+has_foreign_data_wrapper_privilege_name(PG_FUNCTION_ARGS)
+{
+	char	   *fdwname = text_to_cstring(PG_GETARG_TEXT_P(0));
+	text	   *priv_type_text = PG_GETARG_TEXT_P(1);
+
+	return has_foreign_data_wrapper_privilege(GetUserId(),
+											  GetForeignDataWrapperOidByName(fdwname, false),
+											  priv_type_text);
+}
+
+/*
+ * has_foreign_data_wrapper_privilege_name_id
+ *		Check user privileges on a foreign-data wrapper given
+ *		name usename, foreign-data wrapper oid, and text priv name.
+ */
+Datum
+has_foreign_data_wrapper_privilege_name_id(PG_FUNCTION_ARGS)
+{
+	Name		username = PG_GETARG_NAME(0);
+	Oid			fdwid = PG_GETARG_OID(1);
+	text	   *priv_type_text = PG_GETARG_TEXT_P(2);
+
+	if (!SearchSysCacheExists(FOREIGNDATAWRAPPEROID,
+							  ObjectIdGetDatum(fdwid),
+							  0, 0, 0))
+		PG_RETURN_NULL();
+
+	return has_foreign_data_wrapper_privilege(get_roleid_checked(NameStr(*username)),
+											  fdwid, priv_type_text);
+}
+
+/*
+ * has_foreign_data_wrapper_privilege_id
+ *		Check user privileges on a foreign-data wrapper given
+ *		foreign-data wrapper oid, and text priv name.
+ *		current_user is assumed
+ */
+Datum
+has_foreign_data_wrapper_privilege_id(PG_FUNCTION_ARGS)
+{
+	Oid			fdwid = PG_GETARG_OID(0);
+	text	   *priv_type_text = PG_GETARG_TEXT_P(1);
+
+	if (!SearchSysCacheExists(FOREIGNDATAWRAPPEROID,
+							  ObjectIdGetDatum(fdwid),
+							  0, 0, 0))
+		PG_RETURN_NULL();
+
+	return has_foreign_data_wrapper_privilege(GetUserId(), fdwid,
+											  priv_type_text);
+}
+
+/*
+ * has_foreign_data_wrapper_privilege_id_name
+ *		Check user privileges on a foreign-data wrapper given
+ *		roleid, text fdwname, and text priv name.
+ */
+Datum
+has_foreign_data_wrapper_privilege_id_name(PG_FUNCTION_ARGS)
+{
+	Oid			roleid = PG_GETARG_OID(0);
+	char	   *fdwname = text_to_cstring(PG_GETARG_TEXT_P(1));
+	text	   *priv_type_text = PG_GETARG_TEXT_P(2);
+
+	return has_foreign_data_wrapper_privilege(roleid,
+											  GetForeignDataWrapperOidByName(fdwname, false),
+											  priv_type_text);
+}
+
+/*
+ * has_foreign_data_wrapper_privilege_id_id
+ *		Check user privileges on a foreign-data wrapper given
+ *		roleid, fdw oid, and text priv name.
+ */
+Datum
+has_foreign_data_wrapper_privilege_id_id(PG_FUNCTION_ARGS)
+{
+	Oid			roleid = PG_GETARG_OID(0);
+	Oid			fdwid = PG_GETARG_OID(1);
+	text	   *priv_type_text = PG_GETARG_TEXT_P(2);
+
+	if (!SearchSysCacheExists(FOREIGNDATAWRAPPEROID,
+							  ObjectIdGetDatum(fdwid),
+							  0, 0, 0))
+		PG_RETURN_NULL();
+
+	return has_foreign_data_wrapper_privilege(roleid, fdwid, priv_type_text);
+}
+
+
 /*
  * has_function_privilege variants
  *		These are all named "has_function_privilege" at the SQL level.
@@ -2466,6 +2625,154 @@ convert_schema_priv_string(text *priv_type_text)
 	return ACL_NO_RIGHTS;		/* keep compiler quiet */
 }
 
+/*
+ * has_server_privilege variants
+ *		These are all named "has_server_privilege" at the SQL level.
+ *		They take various combinations of foreign server name,
+ *		server OID, user name, user OID, or implicit user = current_user.
+ *
+ *		The result is a boolean value: true if user has the indicated
+ *		privilege, false if not.
+ */
+
+/*
+ * has_server_privilege
+ *		Check user privileges on a foreign server.
+ */
+static Datum
+has_server_privilege(Oid roleid, Oid serverid, text *priv_type_text)
+{
+	AclResult	aclresult;
+	AclMode		mode = ACL_NO_RIGHTS;
+	char	   *priv_type = text_to_cstring(priv_type_text);
+
+	if (pg_strcasecmp(priv_type, "USAGE") == 0)
+		mode = ACL_USAGE;
+	else if (pg_strcasecmp(priv_type, "USAGE WITH GRANT OPTION") == 0)
+		mode = ACL_GRANT_OPTION_FOR(ACL_USAGE);
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized privilege type: \"%s\"", priv_type)));
+
+	aclresult = pg_foreign_server_aclcheck(serverid, roleid, mode);
+
+	PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_server_privilege_name_name
+ *		Check user privileges on a foreign server given
+ *		name username, text servername, and text priv name.
+ */
+Datum
+has_server_privilege_name_name(PG_FUNCTION_ARGS)
+{
+	Name		username = PG_GETARG_NAME(0);
+	char	   *servername = text_to_cstring(PG_GETARG_TEXT_P(1));
+	text	   *priv_type_text = PG_GETARG_TEXT_P(2);
+
+	return has_server_privilege(get_roleid_checked(NameStr(*username)),
+								GetForeignServerOidByName(servername, false),
+								priv_type_text);
+}
+
+/*
+ * has_server_privilege_name
+ *		Check user privileges on a foreign server given
+ *		text servername and text priv name.
+ *		current_user is assumed
+ */
+Datum
+has_server_privilege_name(PG_FUNCTION_ARGS)
+{
+	char	   *servername = text_to_cstring(PG_GETARG_TEXT_P(0));
+	text	   *priv_type_text = PG_GETARG_TEXT_P(1);
+
+	return has_server_privilege(GetUserId(),
+								GetForeignServerOidByName(servername, false),
+								priv_type_text);
+}
+
+/*
+ * has_server_privilege_name_id
+ *		Check user privileges on a foreign server given
+ *		name usename, foreign server oid, and text priv name.
+ */
+Datum
+has_server_privilege_name_id(PG_FUNCTION_ARGS)
+{
+	Name		username = PG_GETARG_NAME(0);
+	Oid			serverid = PG_GETARG_OID(1);
+	text	   *priv_type_text = PG_GETARG_TEXT_P(2);
+
+	if (!SearchSysCacheExists(FOREIGNSERVEROID,
+							  ObjectIdGetDatum(serverid),
+							  0, 0, 0))
+		PG_RETURN_NULL();
+
+	return has_server_privilege(get_roleid_checked(NameStr(*username)), serverid,
+								priv_type_text);
+}
+
+/*
+ * has_server_privilege_id
+ *		Check user privileges on a foreign server given
+ *		server oid, and text priv name.
+ *		current_user is assumed
+ */
+Datum
+has_server_privilege_id(PG_FUNCTION_ARGS)
+{
+	Oid			serverid = PG_GETARG_OID(0);
+	text	   *priv_type_text = PG_GETARG_TEXT_P(1);
+
+	if (!SearchSysCacheExists(FOREIGNSERVEROID,
+							  ObjectIdGetDatum(serverid),
+							  0, 0, 0))
+		PG_RETURN_NULL();
+
+	return has_server_privilege(GetUserId(), serverid, priv_type_text);
+}
+
+/*
+ * has_server_privilege_id_name
+ *		Check user privileges on a foreign server given
+ *		roleid, text servername, and text priv name.
+ */
+Datum
+has_server_privilege_id_name(PG_FUNCTION_ARGS)
+{
+	Oid			roleid = PG_GETARG_OID(0);
+	char	   *servername = text_to_cstring(PG_GETARG_TEXT_P(1));
+	text	   *priv_type_text = PG_GETARG_TEXT_P(2);
+
+	return has_server_privilege(roleid,
+								GetForeignServerOidByName(servername, false),
+								priv_type_text);
+}
+
+/*
+ * has_server_privilege_id_id
+ *		Check user privileges on a foreign server given
+ *		roleid, server oid, and text priv name.
+ */
+Datum
+has_server_privilege_id_id(PG_FUNCTION_ARGS)
+{
+	Oid			roleid = PG_GETARG_OID(0);
+	Oid			serverid = PG_GETARG_OID(1);
+	text	   *priv_type_text = PG_GETARG_TEXT_P(2);
+
+	if (!SearchSysCacheExists(FOREIGNSERVEROID,
+							  ObjectIdGetDatum(serverid),
+							  0, 0, 0))
+		PG_RETURN_NULL();
+
+	return has_server_privilege(roleid, serverid, priv_type_text);
+}
+
+
 /*
  * has_tablespace_privilege variants
  *		These are all named "has_tablespace_privilege" at the SQL level.
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 1ccf7b8d69b24a2aaec01fd4e93b71b069dcf47f..3502245687962c37cf4ff25a2be200bbfbee91ed 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/cache/syscache.c,v 1.117 2008/06/19 00:46:05 alvherre Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/syscache.c,v 1.118 2008/12/19 16:25:17 petere Exp $
  *
  * NOTES
  *	  These routines allow the parser/planner/executor to perform
@@ -32,6 +32,8 @@
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_enum.h"
+#include "catalog/pg_foreign_data_wrapper.h"
+#include "catalog/pg_foreign_server.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
@@ -46,6 +48,7 @@
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_user_mapping.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
@@ -365,6 +368,54 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		256
 	},
+	{ForeignDataWrapperRelationId,                  /* FOREIGNDATAWRAPPERNAME */
+		ForeignDataWrapperNameIndexId,
+		0,
+		1,
+		{
+			Anum_pg_foreign_data_wrapper_fdwname,
+			0,
+			0,
+			0
+		},
+		8
+	},
+	{ForeignDataWrapperRelationId,                  /* FOREIGNDATAWRAPPEROID */
+		ForeignDataWrapperOidIndexId,
+		0,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		8
+	},
+	{ForeignServerRelationId,                       /* FOREIGNSERVERNAME */
+		ForeignServerNameIndexId,
+		0,
+		1,
+		{
+			Anum_pg_foreign_server_srvname,
+			0,
+			0,
+			0
+		},
+		32
+	},
+	{ForeignServerRelationId,                       /* FOREIGNSERVEROID */
+		ForeignServerOidIndexId,
+		0,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		32
+	},
 	{IndexRelationId,			/* INDEXRELID */
 		IndexRelidIndexId,
 		Anum_pg_index_indrelid,
@@ -676,6 +727,30 @@ static const struct cachedesc cacheinfo[] = {
 			0
 		},
 		1024
+	},
+	{UserMappingRelationId,                  /* USERMAPPINGOID */
+		UserMappingOidIndexId,
+		0,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		128
+	},
+	{UserMappingRelationId,                  /* USERMAPPINGUSERSERVER */
+		UserMappingUserServerIndexId,
+		0,
+		2,
+		{
+			Anum_pg_user_mapping_umuser,
+			Anum_pg_user_mapping_umserver,
+			0,
+			0
+		},
+		128
 	}
 };
 
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 8950dee0ca867cfe9fce10b55fab23189e8bbf67..3502c0481611f5fa7af9e53d2dcf2c27b236c0e6 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/bin/pg_dump/common.c,v 1.104 2008/05/09 23:32:04 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/bin/pg_dump/common.c,v 1.105 2008/12/19 16:25:17 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -91,6 +91,8 @@ getSchemaData(int *numTablesPtr)
 	TSTemplateInfo *tmplinfo;
 	TSDictInfo *dictinfo;
 	TSConfigInfo *cfginfo;
+	FdwInfo	   *fdwinfo;
+	ForeignServerInfo *srvinfo;
 	int			numNamespaces;
 	int			numAggregates;
 	int			numInherits;
@@ -104,6 +106,8 @@ getSchemaData(int *numTablesPtr)
 	int			numTSTemplates;
 	int			numTSDicts;
 	int			numTSConfigs;
+	int			numForeignDataWrappers;
+	int			numForeignServers;
 
 	if (g_verbose)
 		write_msg(NULL, "reading schemas\n");
@@ -154,6 +158,14 @@ getSchemaData(int *numTablesPtr)
 		write_msg(NULL, "reading user-defined text search configurations\n");
 	cfginfo = getTSConfigurations(&numTSConfigs);
 
+	if (g_verbose)
+		write_msg(NULL, "reading user-defined foreign-data wrappers\n");
+	fdwinfo = getForeignDataWrappers(&numForeignDataWrappers);
+
+	if (g_verbose)
+		write_msg(NULL, "reading user-defined foreign servers\n");
+	srvinfo = getForeignServers(&numForeignServers);
+
 	if (g_verbose)
 		write_msg(NULL, "reading user-defined operator families\n");
 	opfinfo = getOpfamilies(&numOpfamilies);
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index bf6fa6e445b7b9cc3ec1394c12ff368c0f641fef..d6143e96bb05a2c6c0e62c479db5cbd178a05b22 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/bin/pg_dump/dumputils.c,v 1.41 2008/09/08 00:47:40 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/dumputils.c,v 1.42 2008/12/19 16:25:17 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -687,6 +687,10 @@ do { \
 	}
 	else if (strcmp(type, "TABLESPACE") == 0)
 		CONVERT_PRIV('C', "CREATE");
+	else if (strcmp(type, "FOREIGN DATA WRAPPER") == 0)
+		CONVERT_PRIV('U', "USAGE");
+	else if (strcmp(type, "SERVER") == 0)
+		CONVERT_PRIV('U', "USAGE");
 	else
 		abort();
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index e25fcae031d142d65d9d81110aed92e4620d27ba..ec14f1b9426047ad686b723c0c0bde1105ef701b 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *		$PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_archiver.c,v 1.158 2008/09/05 23:53:42 momjian Exp $
+ *		$PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_archiver.c,v 1.159 2008/12/19 16:25:17 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2479,7 +2479,10 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
 	/* objects named by just a name */
 	if (strcmp(type, "DATABASE") == 0 ||
 		strcmp(type, "PROCEDURAL LANGUAGE") == 0 ||
-		strcmp(type, "SCHEMA") == 0)
+		strcmp(type, "SCHEMA") == 0 ||
+		strcmp(type, "FOREIGN DATA WRAPPER") == 0 ||
+		strcmp(type, "SERVER") == 0 ||
+		strcmp(type, "USER MAPPING") == 0)
 	{
 		appendPQExpBuffer(buf, "%s %s", type, fmtId(te->tag));
 		return;
@@ -2636,7 +2639,9 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
 			strcmp(te->desc, "VIEW") == 0 ||
 			strcmp(te->desc, "SEQUENCE") == 0 ||
 			strcmp(te->desc, "TEXT SEARCH DICTIONARY") == 0 ||
-			strcmp(te->desc, "TEXT SEARCH CONFIGURATION") == 0)
+			strcmp(te->desc, "TEXT SEARCH CONFIGURATION") == 0 ||
+			strcmp(te->desc, "FOREIGN DATA WRAPPER") == 0 ||
+			strcmp(te->desc, "SERVER") == 0)
 		{
 			PQExpBuffer temp = createPQExpBuffer();
 
@@ -2653,7 +2658,8 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
 				 strcmp(te->desc, "FK CONSTRAINT") == 0 ||
 				 strcmp(te->desc, "INDEX") == 0 ||
 				 strcmp(te->desc, "RULE") == 0 ||
-				 strcmp(te->desc, "TRIGGER") == 0)
+				 strcmp(te->desc, "TRIGGER") == 0 ||
+				 strcmp(te->desc, "USER MAPPING") == 0)
 		{
 			/* these object types don't have separate owners */
 		}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 04758761d4ee2b2a18b5f1574cc44447d7a53461..ae7233dbf83316f179964715bc2f99a31688c219 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12,7 +12,7 @@
  *	by PostgreSQL
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.508 2008/12/18 18:20:34 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.509 2008/12/19 16:25:18 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -158,6 +158,11 @@ static void dumpTSParser(Archive *fout, TSParserInfo *prsinfo);
 static void dumpTSDictionary(Archive *fout, TSDictInfo *dictinfo);
 static void dumpTSTemplate(Archive *fout, TSTemplateInfo *tmplinfo);
 static void dumpTSConfig(Archive *fout, TSConfigInfo *cfginfo);
+static void dumpForeignDataWrapper(Archive *fout, FdwInfo *fdwinfo);
+static void dumpForeignServer(Archive *fout, ForeignServerInfo *srvinfo);
+static void dumpUserMappings(Archive *fout, const char *target,
+			const char *servername, const char *namespace,
+			const char *owner, CatalogId catalogId, DumpId dumpId);
 
 static void dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId,
 		const char *type, const char *name,
@@ -5269,6 +5274,165 @@ getTSConfigurations(int *numTSConfigs)
 	return cfginfo;
 }
 
+/*
+ * getForeignDataWrappers:
+ *	  read all foreign-data wrappers in the system catalogs and return
+ *	  them in the FdwInfo* structure
+ *
+ *	numForeignDataWrappers is set to the number of fdws read in
+ */
+FdwInfo *
+getForeignDataWrappers(int *numForeignDataWrappers)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	FdwInfo	   *fdwinfo;
+	int			i_oid;
+	int			i_fdwname;
+	int			i_rolname;
+	int			i_fdwlibrary;
+	int			i_fdwacl;
+	int			i_fdwoptions;
+
+	/* Before 8.4, there are no foreign-data wrappers */
+	if (g_fout->remoteVersion < 80400)
+	{
+		*numForeignDataWrappers = 0;
+		return NULL;
+	}
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema("pg_catalog");
+
+	appendPQExpBuffer(query, "SELECT oid, fdwname, "
+					  "(%s fdwowner) as rolname, fdwlibrary, 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);
+
+	ntups = PQntuples(res);
+	*numForeignDataWrappers = ntups;
+
+	fdwinfo = (FdwInfo *) malloc(ntups * sizeof(FdwInfo));
+
+	i_oid = PQfnumber(res, "oid");
+	i_fdwname = PQfnumber(res, "fdwname");
+	i_rolname = PQfnumber(res, "rolname");
+	i_fdwlibrary = PQfnumber(res, "fdwlibrary");
+	i_fdwacl = PQfnumber(res, "fdwacl");
+	i_fdwoptions = PQfnumber(res, "fdwoptions");
+
+	for (i = 0; i < ntups; i++)
+	{
+		fdwinfo[i].dobj.objType = DO_FDW;
+		fdwinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+		AssignDumpId(&fdwinfo[i].dobj);
+		fdwinfo[i].dobj.name = strdup(PQgetvalue(res, i, i_fdwname));
+		fdwinfo[i].dobj.namespace = NULL;
+		fdwinfo[i].rolname = strdup(PQgetvalue(res, i, i_rolname));
+		fdwinfo[i].fdwlibrary = strdup(PQgetvalue(res, i, i_fdwlibrary));
+		fdwinfo[i].fdwoptions = strdup(PQgetvalue(res, i, i_fdwoptions));
+		fdwinfo[i].fdwacl = strdup(PQgetvalue(res, i, i_fdwacl));
+
+
+		/* Decide whether we want to dump it */
+		selectDumpableObject(&(fdwinfo[i].dobj));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return fdwinfo;
+}
+
+/*
+ * getForeignServers:
+ *	  read all foreign servers in the system catalogs and return
+ *	  them in the ForeignServerInfo * structure
+ *
+ *	numForeignServers is set to the number of servers read in
+ */
+ForeignServerInfo *
+getForeignServers(int *numForeignServers)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query = createPQExpBuffer();
+	ForeignServerInfo *srvinfo;
+	int			i_oid;
+	int			i_srvname;
+	int			i_rolname;
+	int			i_srvfdw;
+	int			i_srvtype;
+	int			i_srvversion;
+	int			i_srvacl;
+	int			i_srvoptions;
+
+	/* Before 8.4, there are no foreign servers */
+	if (g_fout->remoteVersion < 80400)
+	{
+		*numForeignServers = 0;
+		return NULL;
+	}
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema("pg_catalog");
+
+	appendPQExpBuffer(query, "SELECT oid, srvname, "
+					  "(%s srvowner) as rolname, "
+					  "srvfdw, srvtype, srvversion, srvacl,"
+					  "array_to_string(ARRAY(select option_name || ' ' || quote_literal(option_value) from pg_options_to_table(srvoptions)), ', ') as srvoptions "
+					  "FROM pg_foreign_server",
+					  username_subquery);
+
+	res = PQexec(g_conn, query->data);
+	check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+	*numForeignServers = ntups;
+
+	srvinfo = (ForeignServerInfo *) malloc(ntups * sizeof(ForeignServerInfo));
+
+	i_oid = PQfnumber(res, "oid");
+	i_srvname = PQfnumber(res, "srvname");
+	i_rolname = PQfnumber(res, "rolname");
+	i_srvfdw = PQfnumber(res, "srvfdw");
+	i_srvtype = PQfnumber(res, "srvtype");
+	i_srvversion = PQfnumber(res, "srvversion");
+	i_srvacl = PQfnumber(res, "srvacl");
+	i_srvoptions = PQfnumber(res, "srvoptions");
+
+	for (i = 0; i < ntups; i++)
+	{
+		srvinfo[i].dobj.objType = DO_FOREIGN_SERVER;
+		srvinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+		AssignDumpId(&srvinfo[i].dobj);
+		srvinfo[i].dobj.name = strdup(PQgetvalue(res, i, i_srvname));
+		srvinfo[i].dobj.namespace = NULL;
+		srvinfo[i].rolname = strdup(PQgetvalue(res, i, i_rolname));
+		srvinfo[i].srvfdw = atooid(PQgetvalue(res, i, i_srvfdw));
+		srvinfo[i].srvtype = strdup(PQgetvalue(res, i, i_srvtype));
+		srvinfo[i].srvversion = strdup(PQgetvalue(res, i, i_srvversion));
+		srvinfo[i].srvoptions = strdup(PQgetvalue(res, i, i_srvoptions));
+		srvinfo[i].srvacl = strdup(PQgetvalue(res, i, i_srvacl));
+
+		/* Decide whether we want to dump it */
+		selectDumpableObject(&(srvinfo[i].dobj));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return srvinfo;
+}
 
 /*
  * dumpComment --
@@ -5671,6 +5835,12 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_TSCONFIG:
 			dumpTSConfig(fout, (TSConfigInfo *) dobj);
 			break;
+		case DO_FDW:
+			dumpForeignDataWrapper(fout, (FdwInfo *) dobj);
+			break;
+		case DO_FOREIGN_SERVER:
+			dumpForeignServer(fout, (ForeignServerInfo *) dobj);
+			break;
 		case DO_BLOBS:
 			ArchiveEntry(fout, dobj->catId, dobj->dumpId,
 						 dobj->name, NULL, NULL, "",
@@ -8977,6 +9147,224 @@ dumpTSConfig(Archive *fout, TSConfigInfo *cfginfo)
 	destroyPQExpBuffer(query);
 }
 
+/*
+ * dumpForeignDataWrapper
+ *	  write out a single foreign-data wrapper definition
+ */
+static void
+dumpForeignDataWrapper(Archive *fout, FdwInfo *fdwinfo)
+{
+	PQExpBuffer q;
+	PQExpBuffer delq;
+	char	   *namecopy;
+
+	/* Skip if not to be dumped */
+	if (!fdwinfo->dobj.dump || dataOnly)
+		return;
+
+	q = createPQExpBuffer();
+	delq = createPQExpBuffer();
+
+	appendPQExpBuffer(q, "CREATE FOREIGN DATA WRAPPER %s LIBRARY '%s' LANGUAGE C",
+					  fmtId(fdwinfo->dobj.name), fdwinfo->fdwlibrary);
+	if (fdwinfo->fdwoptions && strlen(fdwinfo->fdwoptions) > 0)
+		appendPQExpBuffer(q, " OPTIONS (%s)", fdwinfo->fdwoptions);
+
+	appendPQExpBuffer(q, ";\n");
+
+	appendPQExpBuffer(delq, "DROP FOREIGN DATA WRAPPER %s;\n",
+					  fmtId(fdwinfo->dobj.name));
+
+	ArchiveEntry(fout, fdwinfo->dobj.catId, fdwinfo->dobj.dumpId,
+				 fdwinfo->dobj.name,
+				 NULL,
+				 NULL,
+				 fdwinfo->rolname,
+				 false, "FOREIGN DATA WRAPPER", q->data, delq->data, NULL,
+				 fdwinfo->dobj.dependencies, fdwinfo->dobj.nDeps,
+				 NULL, NULL);
+
+	/* Handle the ACL */
+	namecopy = strdup(fmtId(fdwinfo->dobj.name));
+	dumpACL(fout, fdwinfo->dobj.catId, fdwinfo->dobj.dumpId,
+			"FOREIGN DATA WRAPPER",
+			namecopy, fdwinfo->dobj.name,
+			NULL, fdwinfo->rolname,
+			fdwinfo->fdwacl);
+	free(namecopy);
+
+	destroyPQExpBuffer(q);
+	destroyPQExpBuffer(delq);
+}
+
+/*
+ * dumpForeignServer
+ *	  write out a foreign server definition
+ */
+static void
+dumpForeignServer(Archive *fout, ForeignServerInfo *srvinfo)
+{
+	PQExpBuffer q;
+	PQExpBuffer delq;
+	PQExpBuffer query;
+	PGresult   *res;
+	int			ntups;
+	char	   *namecopy;
+	char	   *fdwname;
+
+	/* Skip if not to be dumped */
+	if (!srvinfo->dobj.dump || dataOnly)
+		return;
+
+	q = createPQExpBuffer();
+	delq = createPQExpBuffer();
+	query = createPQExpBuffer();
+
+	/* look up the foreign-data wrapper */
+	appendPQExpBuffer(query, "SELECT fdwname "
+					  "FROM pg_foreign_data_wrapper w "
+					  "WHERE w.oid = '%u'",
+					  srvinfo->srvfdw);
+	res = PQexec(g_conn, query->data);
+	check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK);
+	ntups = PQntuples(res);
+	if (ntups != 1)
+	{
+		write_msg(NULL, "query returned %d rows instead of one: %s\n",
+				  ntups, query->data);
+		exit_nicely();
+	}
+	fdwname = PQgetvalue(res, 0, 0);
+
+	appendPQExpBuffer(q, "CREATE SERVER %s", fmtId(srvinfo->dobj.name));
+	if (srvinfo->srvtype && strlen(srvinfo->srvtype) > 0)
+		appendPQExpBuffer(q, " TYPE '%s'", srvinfo->srvtype);
+	if (srvinfo->srvversion && strlen(srvinfo->srvversion) > 0)
+		appendPQExpBuffer(q, " VERSION '%s'", srvinfo->srvversion);
+
+	appendPQExpBuffer(q, " FOREIGN DATA WRAPPER ");
+	appendPQExpBuffer(q, "%s", fmtId(fdwname));
+
+	if (srvinfo->srvoptions && strlen(srvinfo->srvoptions) > 0)
+		appendPQExpBuffer(q, " OPTIONS (%s)", srvinfo->srvoptions);
+
+	appendPQExpBuffer(q, ";\n");
+
+	appendPQExpBuffer(delq, "DROP SERVER %s;\n",
+					  fmtId(srvinfo->dobj.name));
+
+	ArchiveEntry(fout, srvinfo->dobj.catId, srvinfo->dobj.dumpId,
+				 srvinfo->dobj.name,
+				 NULL,
+				 NULL,
+				 srvinfo->rolname,
+				 false, "SERVER", q->data, delq->data, NULL,
+				 srvinfo->dobj.dependencies, srvinfo->dobj.nDeps,
+				 NULL, NULL);
+
+	/* Handle the ACL */
+	namecopy = strdup(fmtId(srvinfo->dobj.name));
+	dumpACL(fout, srvinfo->dobj.catId, srvinfo->dobj.dumpId,
+			"SERVER",
+			namecopy, srvinfo->dobj.name,
+			NULL, srvinfo->rolname,
+			srvinfo->srvacl);
+	free(namecopy);
+
+	/* Dump user mappings */
+	resetPQExpBuffer(q);
+	appendPQExpBuffer(q, "SERVER %s", fmtId(srvinfo->dobj.name));
+	dumpUserMappings(fout, q->data,
+					 srvinfo->dobj.name, NULL,
+					 srvinfo->rolname,
+				     srvinfo->dobj.catId, srvinfo->dobj.dumpId);
+
+	destroyPQExpBuffer(q);
+	destroyPQExpBuffer(delq);
+}
+
+/*
+ * dumpUserMappings
+ *
+ * This routine is used to dump any user mappings associated with the
+ * server handed to this routine. Should be called after ArchiveEntry()
+ * for the server.
+ */
+static void
+dumpUserMappings(Archive *fout, const char *target,
+			const char *servername, const char *namespace,
+			const char *owner,
+			CatalogId catalogId, DumpId dumpId)
+{
+	PQExpBuffer q;
+	PQExpBuffer delq;
+	PQExpBuffer query;
+	PQExpBuffer tag;
+	PGresult   *res;
+	int			ntups;
+	int			i_umuser;
+	int			i_umoptions;
+	int			i;
+
+	q = createPQExpBuffer();
+	tag = createPQExpBuffer();
+	delq = createPQExpBuffer();
+	query = createPQExpBuffer();
+
+	appendPQExpBuffer(query,
+					  "SELECT (%s umuser) AS umuser, "
+					  "array_to_string(ARRAY(SELECT option_name || ' ' || quote_literal(option_value) FROM pg_options_to_table(umoptions)), ', ') AS umoptions\n"
+					  "FROM pg_user_mapping WHERE umserver=%u",
+					  username_subquery,
+					  catalogId.oid);
+
+	res = PQexec(g_conn, query->data);
+	check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+	i_umuser = PQfnumber(res, "umuser");
+	i_umoptions = PQfnumber(res, "umoptions");
+
+	for (i = 0; i < ntups; i++)
+	{
+		char	   *umuser;
+		char       *umoptions;
+
+		umuser = PQgetvalue(res, i, i_umuser);
+		umoptions = PQgetvalue(res, i, i_umoptions);
+
+		resetPQExpBuffer(q);
+		appendPQExpBuffer(q, "CREATE USER MAPPING FOR %s", fmtId(umuser));
+		appendPQExpBuffer(q, " SERVER %s", fmtId(servername));
+
+		if (umoptions && strlen(umoptions) > 0)
+			appendPQExpBuffer(q, " OPTIONS (%s)", umoptions);
+
+		appendPQExpBuffer(q, ";\n");
+
+		resetPQExpBuffer(delq);
+		appendPQExpBuffer(delq, "DROP USER MAPPING FOR %s SERVER %s;\n", fmtId(umuser), fmtId(servername));
+
+		resetPQExpBuffer(tag);
+		appendPQExpBuffer(tag, "USER MAPPING %s %s", fmtId(umuser), target);
+
+		ArchiveEntry(fout, nilCatalogId, createDumpId(),
+					 tag->data,
+					 namespace,
+					 NULL,
+					 owner, false,
+					 "USER MAPPING", q->data,
+					 delq->data, NULL,
+					 &dumpId, 1,
+					 NULL, NULL);
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+	destroyPQExpBuffer(delq);
+	destroyPQExpBuffer(q);
+}
 
 /*----------
  * Write out grant/revoke information
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 9684163e3d58b26c26061a7c4a9b3b01fe05e509..a22e5abeb4f5bc20455122d42fa9856a9cd11073 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.143 2008/11/09 21:24:33 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.144 2008/12/19 16:25:18 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -131,6 +131,8 @@ typedef enum
 	DO_TSDICT,
 	DO_TSTEMPLATE,
 	DO_TSCONFIG,
+	DO_FDW,
+	DO_FOREIGN_SERVER,
 	DO_BLOBS,
 	DO_BLOB_COMMENTS
 } DumpableObjectType;
@@ -418,6 +420,26 @@ typedef struct _cfgInfo
 	Oid			cfgparser;
 } TSConfigInfo;
 
+typedef struct _fdwInfo
+{
+	DumpableObject dobj;
+	char	   *rolname;
+	char	   *fdwlibrary;
+	char	   *fdwoptions;
+	char	   *fdwacl;
+} FdwInfo;
+
+typedef struct _foreignServerInfo
+{
+	DumpableObject dobj;
+	char	   *rolname;
+	Oid			srvfdw;
+	char	   *srvtype;
+	char	   *srvversion;
+	char	   *srvacl;
+	char	   *srvoptions;
+} ForeignServerInfo;
+
 /* global decls */
 extern bool force_quotes;		/* double-quotes for identifiers flag */
 extern bool g_verbose;			/* verbose flag */
@@ -500,5 +522,7 @@ extern TSParserInfo *getTSParsers(int *numTSParsers);
 extern TSDictInfo *getTSDictionaries(int *numTSDicts);
 extern TSTemplateInfo *getTSTemplates(int *numTSTemplates);
 extern TSConfigInfo *getTSConfigurations(int *numTSConfigs);
+extern FdwInfo *getForeignDataWrappers(int *numForeignDataWrappers);
+extern ForeignServerInfo *getForeignServers(int *numForeignServers);
 
 #endif   /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 7206d4eef4619d5d173383bf0817a50ca487b89b..52ece6f6ab766e334f6980973d759d627a222c8c 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump_sort.c,v 1.21 2008/09/08 15:26:23 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump_sort.c,v 1.22 2008/12/19 16:25:18 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -50,6 +50,8 @@ static const int oldObjectTypePriority[] =
 	4,							/* DO_TSDICT */
 	3,							/* DO_TSTEMPLATE */
 	5,							/* DO_TSCONFIG */
+	3,							/* DO_FDW */
+	4,							/* DO_FOREIGN_SERVER */
 	10,							/* DO_BLOBS */
 	11							/* DO_BLOB_COMMENTS */
 };
@@ -84,6 +86,8 @@ static const int newObjectTypePriority[] =
 	6,							/* DO_TSDICT */
 	5,							/* DO_TSTEMPLATE */
 	7,							/* DO_TSCONFIG */
+	3,							/* DO_FDW */
+	4,							/* DO_FOREIGN_SERVER */
 	14,							/* DO_BLOBS */
 	15							/* DO_BLOB_COMMENTS */
 };
@@ -1123,6 +1127,16 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "TEXT SEARCH CONFIGURATION %s  (ID %d OID %u)",
 					 obj->name, obj->dumpId, obj->catId.oid);
 			return;
+		case DO_FDW:
+			snprintf(buf, bufsize,
+					 "FOREIGN DATA WRAPPER %s  (ID %d OID %u)",
+					 obj->name, obj->dumpId, obj->catId.oid);
+			return;
+		case DO_FOREIGN_SERVER:
+			snprintf(buf, bufsize,
+					 "FOREIGN SERVER %s  (ID %d OID %u)",
+					 obj->name, obj->dumpId, obj->catId.oid);
+			return;
 		case DO_BLOBS:
 			snprintf(buf, bufsize,
 					 "BLOBS  (ID %d)",
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 12b2f43abc3a282d2962cc22253f01f20ef7f4f3..147ee9125a1b4586707ac9a94ff1df6b88b6d953 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.198 2008/11/21 20:14:27 mha Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.199 2008/12/19 16:25:18 petere Exp $
  */
 #include "postgres_fe.h"
 #include "command.h"
@@ -416,7 +416,23 @@ exec_command(const char *cmd,
 						break;
 				}
 				break;
-
+			case 'e':			/* SQL/MED subsystem */
+				switch(cmd[2])
+				{
+					case 's':
+						success = listForeignServers(pattern, show_verbose);
+						break;
+					case 'u':
+						success = listUserMappings(pattern, show_verbose);
+						break;
+					case 'w':
+						success = listForeignDataWrappers(pattern, show_verbose);
+						break;
+					default:
+						status = PSQL_CMD_UNKNOWN;
+						break;
+				}
+				break;
 			default:
 				status = PSQL_CMD_UNKNOWN;
 		}
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 5eb760513eccdfc5c0193396fa71b61b47715009..601e478aee52fe72a2f6894b7a4d662e36779bf2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -8,7 +8,7 @@
  *
  * Copyright (c) 2000-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.189 2008/12/19 14:39:58 alvherre Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.190 2008/12/19 16:25:18 petere Exp $
  */
 #include "postgres_fe.h"
 
@@ -2785,3 +2785,156 @@ describeOneTSConfig(const char *oid, const char *nspname, const char *cfgname,
 	PQclear(res);
 	return true;
 }
+
+
+/*
+ * \dew
+ *
+ * Describes foreign-data wrappers
+ */
+bool
+listForeignDataWrappers(const char *pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+
+	initPQExpBuffer(&buf);
+	printfPQExpBuffer(&buf,
+					  "SELECT fdwname AS \"%s\",\n"
+					  "  pg_catalog.pg_get_userbyid(fdwowner) AS \"%s\",\n"
+					  "  fdwlibrary AS \"%s\"\n",
+					  gettext_noop("Name"),
+					  gettext_noop("Owner"),
+					  gettext_noop("Library"));
+
+	if (verbose)
+		appendPQExpBuffer(&buf,
+						  ",\n  fdwacl AS \"%s\","
+						  "  fdwoptions AS \"%s\"",
+						  gettext_noop("Access privileges"),
+						  gettext_noop("Options"));
+
+	appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_foreign_data_wrapper WHERE 1=1\n");
+
+	processSQLNamePattern(pset.db, &buf, pattern, true, false,
+						  NULL, "fdwname", NULL, NULL);
+
+	appendPQExpBuffer(&buf, "ORDER BY 1;");
+
+	res = PSQLexec(buf.data, false);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("List of foreign-data wrappers");
+	myopt.translate_header = true;
+
+	printQuery(res, &myopt, pset.queryFout, pset.logfile);
+
+	PQclear(res);
+	return true;
+}
+
+/*
+ * \des
+ *
+ * Describes servers.
+ */
+bool
+listForeignServers(const char *pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+
+	initPQExpBuffer(&buf);
+	printfPQExpBuffer(&buf,
+					  "SELECT s.srvname AS \"%s\",\n"
+					  "  pg_catalog.pg_get_userbyid(s.srvowner) AS \"%s\",\n"
+					  "  f.fdwname AS \"%s\"\n",
+					  gettext_noop("Name"),
+					  gettext_noop("Owner"),
+					  gettext_noop("Foreign-data wrapper"));
+
+	if (verbose)
+		appendPQExpBuffer(&buf,
+						  ",\n  s.srvacl AS \"%s\","
+						  "  s.srvtype AS \"%s\","
+						  "  s.srvversion AS \"%s\","
+						  "  s.srvoptions AS \"%s\"",
+						  gettext_noop("Access privileges"),
+						  gettext_noop("Type"),
+						  gettext_noop("Version"),
+						  gettext_noop("Options"));
+
+	appendPQExpBuffer(&buf,
+			  		  "\nFROM pg_foreign_server s\n"
+					  "JOIN pg_catalog.pg_foreign_data_wrapper f ON f.oid=s.srvfdw\n");
+
+	processSQLNamePattern(pset.db, &buf, pattern, true, false,
+						  NULL, "s.srvname", NULL, NULL);
+
+	appendPQExpBuffer(&buf, "ORDER BY 1;");
+
+	res = PSQLexec(buf.data, false);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("List of foreign servers");
+	myopt.translate_header = true;
+
+	printQuery(res, &myopt, pset.queryFout, pset.logfile);
+
+	PQclear(res);
+	return true;
+}
+
+/*
+ * \deu
+ *
+ * Describes user mappings.
+ */
+bool
+listUserMappings(const char *pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+
+	initPQExpBuffer(&buf);
+	printfPQExpBuffer(&buf,
+					  "SELECT um.srvname AS \"%s\",\n"
+					  "  um.usename AS \"%s\"",
+					  gettext_noop("Server"),
+					  gettext_noop("Username"));
+
+	if (verbose)
+		appendPQExpBuffer(&buf,
+						  ",\n  um.umoptions AS \"%s\"",
+						  gettext_noop("Options"));
+
+	appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_user_mappings um WHERE 1=1\n");
+
+	processSQLNamePattern(pset.db, &buf, pattern, true, false,
+						  NULL, "um.srvname", "um.usename", NULL);
+
+	appendPQExpBuffer(&buf, "ORDER BY 1, 2;");
+
+	res = PSQLexec(buf.data, false);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("List of user mappings");
+	myopt.translate_header = true;
+
+	printQuery(res, &myopt, pset.queryFout, pset.logfile);
+
+	PQclear(res);
+	return true;
+}
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 76e37d4e7f349690ee1a2c3b3e108a12a0fbe048..a0959576be77b19c390ce525314fa983c203533e 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/describe.h,v 1.35 2008/01/01 19:45:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/describe.h,v 1.36 2008/12/19 16:25:18 petere Exp $
  */
 #ifndef DESCRIBE_H
 #define DESCRIBE_H
@@ -66,5 +66,14 @@ extern bool listCasts(const char *pattern);
 /* \dn */
 extern bool listSchemas(const char *pattern, bool verbose);
 
+/* \dew */
+extern bool listForeignDataWrappers(const char *pattern, bool verbose);
+
+/* \des */
+extern bool listForeignServers(const char *pattern, bool verbose);
+
+/* \deu */
+extern bool listUserMappings(const char *pattern, bool verbose);
+
 
 #endif   /* DESCRIBE_H */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 49db83c03d4f6f3b172868ecfe4fa49c6b839f13..164d17bba56c251e1bed5c4faa1f963adf6ab562 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/help.c,v 1.131 2008/11/06 15:18:36 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/help.c,v 1.132 2008/12/19 16:25:18 petere Exp $
  */
 #include "postgres_fe.h"
 
@@ -203,6 +203,9 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\dC [PATTERN]  list casts\n"));
 	fprintf(output, _("  \\dd [PATTERN]  show comment for object\n"));
 	fprintf(output, _("  \\dD [PATTERN]  list domains\n"));
+	fprintf(output, _("  \\des [PATTERN] list foreign servers (add \"+\" for more detail)\n"));
+	fprintf(output, _("  \\deu [PATTERN] list user mappings (add \"+\" for more detail)\n"));
+	fprintf(output, _("  \\dew [PATTERN] list foreign-data wrappers (add \"+\" for more detail)\n"));
 	fprintf(output, _("  \\df [PATTERN]  list functions (add \"+\" for more detail)\n"));
 	fprintf(output, _("  \\dF [PATTERN]  list text search configurations (add \"+\" for more detail)\n"));
 	fprintf(output, _("  \\dFd [PATTERN] list text search dictionaries (add \"+\" for more detail)\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 635e0c7fa7b25e855a24afba38812722f7e2d907..54363a3e74e3f28aa7304616156154fe089dcf76 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.177 2008/11/20 14:04:46 petere Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.178 2008/12/19 16:25:18 petere Exp $
  */
 
 /*----------------------------------------------------------------------
@@ -497,6 +497,21 @@ static const SchemaQuery Query_for_list_of_views = {
 "SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template "\
 " WHERE substring(pg_catalog.quote_ident(tmplname),1,%d)='%s'"
 
+#define Query_for_list_of_fdws \
+" SELECT pg_catalog.quote_ident(fdwname) "\
+"   FROM pg_catalog.pg_foreign_data_wrapper "\
+"  WHERE substring(pg_catalog.quote_ident(fdwname),1,%d)='%s'"
+
+#define Query_for_list_of_servers \
+" SELECT pg_catalog.quote_ident(srvname) "\
+"   FROM pg_catalog.pg_foreign_server "\
+"  WHERE substring(pg_catalog.quote_ident(srvname),1,%d)='%s'"
+
+#define Query_for_list_of_user_mappings \
+" SELECT pg_catalog.quote_ident(usename) "\
+"   FROM pg_catalog.pg_user_mappings "\
+"  WHERE substring(pg_catalog.quote_ident(usename),1,%d)='%s'"
+
 /*
  * This is a list of all "things" in Pgsql, which can show up after CREATE or
  * DROP; and there is also a query to get a list of them.
@@ -525,6 +540,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"DATABASE", Query_for_list_of_databases},
 	{"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, true},
 	{"DOMAIN", NULL, &Query_for_list_of_domains},
+	{"FOREIGN DATA WRAPPER", NULL, NULL},
 	{"FUNCTION", NULL, &Query_for_list_of_functions},
 	{"GROUP", Query_for_list_of_roles},
 	{"LANGUAGE", Query_for_list_of_languages},
@@ -536,6 +552,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
 	{"SCHEMA", Query_for_list_of_schemas},
 	{"SEQUENCE", NULL, &Query_for_list_of_sequences},
+	{"SERVER", Query_for_list_of_servers},
 	{"TABLE", NULL, &Query_for_list_of_tables},
 	{"TABLESPACE", Query_for_list_of_tablespaces},
 	{"TEMP", NULL, NULL},		/* for CREATE TEMP TABLE ... */
@@ -545,6 +562,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"TYPE", NULL, &Query_for_list_of_datatypes},
 	{"UNIQUE", NULL, NULL},		/* for CREATE UNIQUE INDEX ... */
 	{"USER", Query_for_list_of_roles},
+	{"USER MAPPING FOR", NULL, NULL},
 	{"VIEW", NULL, &Query_for_list_of_views},
 	{NULL, NULL, NULL, false}	/* end of list */
 };
@@ -621,7 +639,7 @@ psql_completion(char *text, int start, int end)
 
 	static const char *const backslash_commands[] = {
 		"\\a", "\\connect", "\\C", "\\cd", "\\copy", "\\copyright",
-		"\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\df",
+		"\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\des", "\\deu", "\\dew", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl",
 		"\\dn", "\\do", "\\dp", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du",
 		"\\e", "\\echo", "\\encoding",
@@ -686,9 +704,9 @@ psql_completion(char *text, int start, int end)
 			 pg_strcasecmp(prev3_wd, "TABLE") != 0)
 	{
 		static const char *const list_ALTER[] =
-		{"AGGREGATE", "CONVERSION", "DATABASE", "DOMAIN", "FUNCTION",
-			"GROUP", "INDEX", "LANGUAGE", "OPERATOR", "ROLE", "SCHEMA", "SEQUENCE", "TABLE",
-		"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", "USER", "VIEW", NULL};
+		{"AGGREGATE", "CONVERSION", "DATABASE", "DOMAIN", "FOREIGN DATA WRAPPER", "FUNCTION",
+		"GROUP", "INDEX", "LANGUAGE", "OPERATOR", "ROLE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE",
+		"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", "USER", "USER MAPPING FOR", "VIEW", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
@@ -724,6 +742,18 @@ psql_completion(char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERDATABASE);
 	}
 
+	/* ALTER FOREIGN DATA WRAPPER <name> */
+	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
+			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
+			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
+	{
+		static const char *const list_ALTER_FDW[] =
+		{"LIBRARY", "OPTIONS", "OWNER TO", NULL};
+
+		COMPLETE_WITH_LIST(list_ALTER_FDW);
+	}
+
 	/* ALTER INDEX <name> */
 	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev2_wd, "INDEX") == 0)
@@ -746,6 +776,7 @@ psql_completion(char *text, int start, int end)
 
 	/* ALTER USER,ROLE <name> */
 	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+			 !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
 			 (pg_strcasecmp(prev2_wd, "USER") == 0 ||
 			  pg_strcasecmp(prev2_wd, "ROLE") == 0))
 	{
@@ -814,6 +845,15 @@ psql_completion(char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_ALTERSEQUENCE2);
 	}
+	/* ALTER SERVER <name> */
+	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
+	{
+		static const char *const list_ALTER_SERVER[] =
+		{"VERSION", "OPTIONS", "OWNER TO", NULL};
+
+		COMPLETE_WITH_LIST(list_ALTER_SERVER);
+	}
 	/* ALTER VIEW <name> */
 	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
@@ -1213,6 +1253,18 @@ psql_completion(char *text, int start, int end)
 			 pg_strcasecmp(prev_wd, "TEMPLATE") == 0)
 		COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
 
+	/* CREATE FOREIGN DATA WRAPPER */
+	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
+			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
+			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
+			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
+		COMPLETE_WITH_CONST("LIBRARY");
+
+	else if (pg_strcasecmp(prev5_wd, "DATA") == 0 &&
+			 pg_strcasecmp(prev4_wd, "WRAPPER") == 0 &&
+			 pg_strcasecmp(prev2_wd, "LIBRARY") == 0)
+		COMPLETE_WITH_CONST("LANGUAGE C");
+
 	/* CREATE INDEX */
 	/* First off we complete CREATE UNIQUE with "INDEX" */
 	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
@@ -1289,6 +1341,16 @@ psql_completion(char *text, int start, int end)
 			 pg_strcasecmp(prev_wd, "TO") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
+/* CREATE SERVER <name> */
+	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
+			 pg_strcasecmp(prev2_wd, "SERVER") == 0)
+	{
+		static const char *const list_CREATE_SERVER[] =
+		{"TYPE", "VERSION", "FOREIGN DATA WRAPPER", NULL};
+
+		COMPLETE_WITH_LIST(list_CREATE_SERVER);
+	}
+
 /* CREATE TABLE */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
 	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
@@ -1357,6 +1419,7 @@ psql_completion(char *text, int start, int end)
 
 /* CREATE ROLE,USER,GROUP */
 	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
+			 !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
 			 (pg_strcasecmp(prev2_wd, "ROLE") == 0 ||
 			  pg_strcasecmp(prev2_wd, "GROUP") == 0 || pg_strcasecmp(prev2_wd, "USER") == 0))
 	{
@@ -1472,12 +1535,17 @@ psql_completion(char *text, int start, int end)
 			   pg_strcasecmp(prev2_wd, "LANGUAGE") == 0 ||
 			   pg_strcasecmp(prev2_wd, "SCHEMA") == 0 ||
 			   pg_strcasecmp(prev2_wd, "SEQUENCE") == 0 ||
+			   pg_strcasecmp(prev2_wd, "SERVER") == 0 ||
 			   pg_strcasecmp(prev2_wd, "TABLE") == 0 ||
 			   pg_strcasecmp(prev2_wd, "TYPE") == 0 ||
 			   pg_strcasecmp(prev2_wd, "VIEW") == 0)) ||
 			 (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
 			  pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 &&
 			  prev_wd[strlen(prev_wd) - 1] == ')') ||
+			 (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
+			  pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
+			  pg_strcasecmp(prev3_wd, "DATA") == 0 &&
+			  pg_strcasecmp(prev2_wd, "WRAPPER") == 0) ||
 			 (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
 			  pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
 			  pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
@@ -1607,6 +1675,14 @@ psql_completion(char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_FROMIN);
 	}
 
+/* FOREIGN DATA WRAPPER */
+	/* applies in ALTER/DROP FDW and in CREATE SERVER */
+	else if (pg_strcasecmp(prev4_wd, "CREATE") != 0 &&
+			 pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
+			 pg_strcasecmp(prev2_wd, "DATA") == 0 &&
+			 pg_strcasecmp(prev_wd, "WRAPPER") == 0)
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+
 /* GRANT && REVOKE*/
 	/* Complete GRANT/REVOKE with a list of privileges */
 	else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
@@ -1640,6 +1716,8 @@ psql_completion(char *text, int start, int end)
 			 pg_strcasecmp(prev_wd, "ON") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv,
 								   " UNION SELECT 'DATABASE'"
+								   " UNION SELECT 'FOREIGN DATA WRAPPER'"
+								   " UNION SELECT 'FOREIGN SERVER'"
 								   " UNION SELECT 'FUNCTION'"
 								   " UNION SELECT 'LANGUAGE'"
 								   " UNION SELECT 'SCHEMA'"
@@ -1759,6 +1837,10 @@ psql_completion(char *text, int start, int end)
 	else if (pg_strcasecmp(prev_wd, "NOTIFY") == 0)
 		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(relname) FROM pg_catalog.pg_listener WHERE substring(pg_catalog.quote_ident(relname),1,%d)='%s'");
 
+/* OPTIONS */
+	else if (pg_strcasecmp(prev_wd, "OPTIONS") == 0)
+		COMPLETE_WITH_CONST("(");
+
 /* OWNER TO  - complete with available roles */
 	else if (pg_strcasecmp(prev2_wd, "OWNER") == 0 &&
 			 pg_strcasecmp(prev_wd, "TO") == 0)
@@ -2012,6 +2094,32 @@ psql_completion(char *text, int start, int end)
 			 pg_strcasecmp(prev4_wd, "UPDATE") == 0)
 		COMPLETE_WITH_CONST("=");
 
+/* USER MAPPING */
+	else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 ||
+			  pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
+			  pg_strcasecmp(prev3_wd, "DROP") == 0) &&
+			 pg_strcasecmp(prev2_wd, "USER") == 0 &&
+			 pg_strcasecmp(prev_wd, "MAPPING") == 0)
+		COMPLETE_WITH_CONST("FOR");
+	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+			 pg_strcasecmp(prev3_wd, "USER") == 0 &&
+			 pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
+			 pg_strcasecmp(prev_wd, "FOR") == 0)
+		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+	else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 ||
+			  pg_strcasecmp(prev4_wd, "DROP") == 0) &&
+			 pg_strcasecmp(prev3_wd, "USER") == 0 &&
+			 pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
+			 pg_strcasecmp(prev_wd, "FOR") == 0)
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+	else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 ||
+			  pg_strcasecmp(prev5_wd, "ALTER") == 0 ||
+			  pg_strcasecmp(prev5_wd, "DROP") == 0) &&
+			 pg_strcasecmp(prev4_wd, "USER") == 0 &&
+			 pg_strcasecmp(prev3_wd, "MAPPING") == 0 &&
+			 pg_strcasecmp(prev2_wd, "FOR") == 0)
+		COMPLETE_WITH_CONST("SERVER");
+
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
@@ -2088,6 +2196,12 @@ psql_completion(char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	else if (strcmp(prev_wd, "\\dD") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+	else if (strcmp(prev_wd, "\\des") == 0 || strcmp(prev_wd, "\\des+") == 0)
+		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+	else if (strcmp(prev_wd, "\\deu") == 0 || strcmp(prev_wd, "\\deu+") == 0)
+		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+	else if (strcmp(prev_wd, "\\dew") == 0 || strcmp(prev_wd, "\\dew+") == 0)
+		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 	else if (strcmp(prev_wd, "\\df") == 0 || strcmp(prev_wd, "\\df+") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 	else if (strcmp(prev_wd, "\\dF") == 0 || strcmp(prev_wd, "\\dF+") == 0)
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 2ee189437f2061190aebef1f326fbdd575c18847..b1b2a12837361588e08f94a8343aa13793bb6f75 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -4,7 +4,7 @@
  *	  "Catalog version number" for PostgreSQL.
  *
  * The catalog version number is used to flag incompatible changes in
- * the PostgreSQL system catalogs.	Whenever anyone changes the format of
+ * the PostgreSQL system catalogs.  Whenever anyone changes the format of
  * a system catalog relation, or adds, deletes, or modifies standard
  * catalog entries in such a way that an updated backend wouldn't work
  * with an old database (or vice versa), the catalog version number
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.511 2008/12/04 17:51:27 petere Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.512 2008/12/19 16:25:18 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200812041
+#define CATALOG_VERSION_NO	200812191
 
 #endif
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8929be6a3426a985338950bfbdaf95c9dc856cfe..8cd28f7e6c8fc8b6fd10ca30bff3cda6b293805d 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.36 2008/06/08 22:41:04 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.37 2008/12/19 16:25:18 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -143,6 +143,9 @@ typedef enum ObjectClass
 	OCLASS_ROLE,				/* pg_authid */
 	OCLASS_DATABASE,			/* pg_database */
 	OCLASS_TBLSPACE,			/* pg_tablespace */
+	OCLASS_FDW,					/* pg_foreign_data_wrapper */
+	OCLASS_FOREIGN_SERVER,		/* pg_foreign_server */
+	OCLASS_USER_MAPPING,		/* pg_user_mapping */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 18defe8ee788cf034a39911f839e3259bb2ae363..49b2c17fa8a5d70fae1b44ce3b4814b640e1dade 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/indexing.h,v 1.103 2008/06/19 00:46:06 alvherre Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/indexing.h,v 1.104 2008/12/19 16:25:18 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -252,6 +252,24 @@ DECLARE_UNIQUE_INDEX(pg_type_oid_index, 2703, on pg_type using btree(oid oid_ops
 DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, on pg_type using btree(typname name_ops, typnamespace oid_ops));
 #define TypeNameNspIndexId	2704
 
+DECLARE_UNIQUE_INDEX(pg_foreign_data_wrapper_oid_index, 112, on pg_foreign_data_wrapper using btree(oid oid_ops));
+#define ForeignDataWrapperOidIndexId	112
+
+DECLARE_UNIQUE_INDEX(pg_foreign_data_wrapper_name_index, 548, on pg_foreign_data_wrapper using btree(fdwname name_ops));
+#define ForeignDataWrapperNameIndexId	548
+
+DECLARE_UNIQUE_INDEX(pg_foreign_server_oid_index, 113, on pg_foreign_server using btree(oid oid_ops));
+#define ForeignServerOidIndexId	113
+
+DECLARE_UNIQUE_INDEX(pg_foreign_server_name_index, 549, on pg_foreign_server using btree(srvname name_ops));
+#define ForeignServerNameIndexId	549
+
+DECLARE_UNIQUE_INDEX(pg_user_mapping_oid_index, 174, on pg_user_mapping using btree(oid oid_ops));
+#define UserMappingOidIndexId	174
+
+DECLARE_UNIQUE_INDEX(pg_user_mapping_user_server_index, 175, on pg_user_mapping using btree(umuser oid_ops, umserver oid_ops));
+#define UserMappingUserServerIndexId	175
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_foreign_data_wrapper.h b/src/include/catalog/pg_foreign_data_wrapper.h
new file mode 100644
index 0000000000000000000000000000000000000000..aa245961f9e5e93d78585888b703f96bd02740ec
--- /dev/null
+++ b/src/include/catalog/pg_foreign_data_wrapper.h
@@ -0,0 +1,62 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_foreign_data_wrapper.h
+ *	  definition of the system "foreign-data wrapper" relation (pg_foreign_data_wrapper)
+ *	  along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_foreign_data_wrapper.h,v 1.1 2008/12/19 16:25:18 petere Exp $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_FOREIGN_DATA_WRAPPER_H
+#define PG_FOREIGN_DATA_WRAPPER_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_foreign_data_wrapper definition.  cpp turns this into
+ *		typedef struct FormData_pg_foreign_data_wrapper
+ * ----------------
+ */
+#define ForeignDataWrapperRelationId	2328
+
+CATALOG(pg_foreign_data_wrapper,2328)
+{
+	NameData	fdwname;		/* foreign-data wrapper name */
+	Oid			fdwowner;		/* FDW owner */
+
+	/* VARIABLE LENGTH FIELDS start here. */
+
+	text		fdwlibrary;		/* FDW shared library location */
+	aclitem		fdwacl[1];		/* access permissions */
+	text		fdwoptions[1];	/* FDW options */
+} FormData_pg_foreign_data_wrapper;
+
+/* ----------------
+ *		Form_pg_fdw corresponds to a pointer to a tuple with
+ *		the format of pg_fdw relation.
+ * ----------------
+ */
+typedef FormData_pg_foreign_data_wrapper *Form_pg_foreign_data_wrapper;
+
+/* ----------------
+ *		compiler constants for pg_fdw
+ * ----------------
+ */
+
+#define Natts_pg_foreign_data_wrapper				5
+#define Anum_pg_foreign_data_wrapper_fdwname		1
+#define Anum_pg_foreign_data_wrapper_fdwowner		2
+#define Anum_pg_foreign_data_wrapper_fdwlibrary		3
+#define Anum_pg_foreign_data_wrapper_fdwacl			4
+#define Anum_pg_foreign_data_wrapper_fdwoptions		5
+
+#endif   /* PG_FOREIGN_DATA_WRAPPER_H */
diff --git a/src/include/catalog/pg_foreign_server.h b/src/include/catalog/pg_foreign_server.h
new file mode 100644
index 0000000000000000000000000000000000000000..cc334c19159073e0e09124cbdddaed92233c35c6
--- /dev/null
+++ b/src/include/catalog/pg_foreign_server.h
@@ -0,0 +1,65 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_foreign_server.h
+ *	  definition of the system "foreign server" relation (pg_foreign_server)
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_foreign_server.h,v 1.1 2008/12/19 16:25:18 petere Exp $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_FOREIGN_SERVER_H
+#define PG_FOREIGN_SERVER_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_foreign_server definition.  cpp turns this into
+ *		typedef struct FormData_pg_foreign_server
+ * ----------------
+ */
+#define ForeignServerRelationId	1417
+
+CATALOG(pg_foreign_server,1417)
+{
+	NameData	srvname;		/* foreign server name */
+	Oid			srvowner;		/* server owner */
+	Oid			srvfdw;			/* server FDW */
+
+	/*
+	 * VARIABLE LENGTH FIELDS start here.  These fields may be NULL, too.
+	 */
+	text		srvtype;
+	text		srvversion;
+	aclitem		srvacl[1];		/* access permissions */
+	text		srvoptions[1];	/* FDW-specific options */
+} FormData_pg_foreign_server;
+
+/* ----------------
+ *		Form_pg_foreign_server corresponds to a pointer to a tuple with
+ *		the format of pg_foreign_server relation.
+ * ----------------
+ */
+typedef FormData_pg_foreign_server *Form_pg_foreign_server;
+
+/* ----------------
+ *		compiler constants for pg_foreign_server
+ * ----------------
+ */
+
+#define Natts_pg_foreign_server					7
+#define Anum_pg_foreign_server_srvname			1
+#define Anum_pg_foreign_server_srvowner			2
+#define Anum_pg_foreign_server_srvfdw			3
+#define Anum_pg_foreign_server_srvtype			4
+#define Anum_pg_foreign_server_srvversion		5
+#define Anum_pg_foreign_server_srvacl			6
+#define Anum_pg_foreign_server_srvoptions		7
+
+#endif   /* PG_FOREIGN_SERVER_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 9d4e6dcfffac589ab03aaf438b001f928cbe3e6a..63bcdc7c06e415aae0e2fc03983961414b2a3b6b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.531 2008/12/18 18:20:34 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.532 2008/12/19 16:25:18 petere Exp $
  *
  * NOTES
  *	  The script catalog/genbki.sh reads this file and generates .bki
@@ -3582,6 +3582,32 @@ DESCR("current user privilege on tablespace by tablespace name");
 DATA(insert OID = 2395 (  has_tablespace_privilege		   PGNSP PGUID 12 1 0 0 f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ has_tablespace_privilege_id _null_ _null_ _null_ ));
 DESCR("current user privilege on tablespace by tablespace oid");
 
+DATA(insert OID = 3000 (  has_foreign_data_wrapper_privilege		   PGNSP PGUID 12 1 0 0 f f t f s 3 0 16 "19 25 25" _null_ _null_ _null_ _null_	has_foreign_data_wrapper_privilege_name_name _null_ _null_ _null_ ));
+DESCR("user privilege on foreign data wrapper by username, foreign data wrapper name");
+DATA(insert OID = 3001 (  has_foreign_data_wrapper_privilege		   PGNSP PGUID 12 1 0 0 f f t f s 3 0 16 "19 26 25" _null_ _null_ _null_ _null_	has_foreign_data_wrapper_privilege_name_id _null_ _null_ _null_ ));
+DESCR("user privilege on foreign data wrapper by username, foreign data wrapper oid");
+DATA(insert OID = 3002 (  has_foreign_data_wrapper_privilege		   PGNSP PGUID 12 1 0 0 f f t f s 3 0 16 "26 25 25" _null_ _null_ _null_ _null_	has_foreign_data_wrapper_privilege_id_name _null_ _null_ _null_ ));
+DESCR("user privilege on foreign data wrapper by user oid, foreign data wrapper name");
+DATA(insert OID = 3003 (  has_foreign_data_wrapper_privilege		   PGNSP PGUID 12 1 0 0 f f t f s 3 0 16 "26 26 25" _null_ _null_ _null_ _null_	has_foreign_data_wrapper_privilege_id_id _null_ _null_ _null_ ));
+DESCR("user privilege on foreign data wrapper by user oid, foreign data wrapper oid");
+DATA(insert OID = 3004 (  has_foreign_data_wrapper_privilege		   PGNSP PGUID 12 1 0 0 f f t f s 2 0 16 "25 25" _null_ _null_ _null_ _null_ has_foreign_data_wrapper_privilege_name _null_ _null_ _null_ ));
+DESCR("current user privilege on foreign data wrapper by foreign data wrapper name");
+DATA(insert OID = 3005 (  has_foreign_data_wrapper_privilege		   PGNSP PGUID 12 1 0 0 f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ has_foreign_data_wrapper_privilege_id _null_ _null_ _null_ ));
+DESCR("current user privilege on foreign data wrapper by foreign data wrapper oid");
+
+DATA(insert OID = 3006 (  has_server_privilege		   PGNSP PGUID 12 1 0 0 f f t f s 3 0 16 "19 25 25" _null_ _null_ _null_ _null_	has_server_privilege_name_name _null_ _null_ _null_ ));
+DESCR("user privilege on server by username, server name");
+DATA(insert OID = 3007 (  has_server_privilege		   PGNSP PGUID 12 1 0 0 f f t f s 3 0 16 "19 26 25" _null_ _null_ _null_ _null_	has_server_privilege_name_id _null_ _null_ _null_ ));
+DESCR("user privilege on server by username, server oid");
+DATA(insert OID = 3008 (  has_server_privilege		   PGNSP PGUID 12 1 0 0 f f t f s 3 0 16 "26 25 25" _null_ _null_ _null_ _null_	has_server_privilege_id_name _null_ _null_ _null_ ));
+DESCR("user privilege on server by user oid, server name");
+DATA(insert OID = 3009 (  has_server_privilege		   PGNSP PGUID 12 1 0 0 f f t f s 3 0 16 "26 26 25" _null_ _null_ _null_ _null_	has_server_privilege_id_id _null_ _null_ _null_ ));
+DESCR("user privilege on server by user oid, server oid");
+DATA(insert OID = 3010 (  has_server_privilege		   PGNSP PGUID 12 1 0 0 f f t f s 2 0 16 "25 25" _null_ _null_ _null_ _null_ has_server_privilege_name _null_ _null_ _null_ ));
+DESCR("current user privilege on server by server name");
+DATA(insert OID = 3011 (  has_server_privilege		   PGNSP PGUID 12 1 0 0 f f t f s 2 0 16 "26 25" _null_ _null_ _null_ _null_ has_server_privilege_id _null_ _null_ _null_ ));
+DESCR("current user privilege on server by server oid");
+
 DATA(insert OID = 2705 (  pg_has_role		PGNSP PGUID 12 1 0 0 f f t f s 3 0 16 "19 19 25" _null_ _null_ _null_ _null_	pg_has_role_name_name _null_ _null_ _null_ ));
 DESCR("user privilege on role by username, role name");
 DATA(insert OID = 2706 (  pg_has_role		PGNSP PGUID 12 1 0 0 f f t f s 3 0 16 "19 26 25" _null_ _null_ _null_ _null_	pg_has_role_name_id _null_ _null_ _null_ ));
@@ -4604,6 +4630,9 @@ DESCR("record greater than or equal");
 DATA(insert OID = 2987 (  btrecordcmp	   PGNSP PGUID 12 1 0 0 f f t f i 2 0 23 "2249 2249" _null_ _null_ _null_ _null_ btrecordcmp _null_ _null_ _null_ ));
 DESCR("btree less-equal-greater");
 
+DATA(insert OID = 2998 (  pg_options_to_table		PGNSP PGUID 12 1 3 0 f f t t s 1 0 2249 "1009" "{1009,25,25}" "{i,o,o}" "{options_array,option_name,option_value}" _null_ pg_options_to_table _null_ _null_ _null_ ));
+DESCR("convert generic options array to name/value table");
+
 
 /*
  * Symbolic values for provolatile column: these indicate whether the result
diff --git a/src/include/catalog/pg_user_mapping.h b/src/include/catalog/pg_user_mapping.h
new file mode 100644
index 0000000000000000000000000000000000000000..e1770e012382345217aecd7f151be1ef9cf1563d
--- /dev/null
+++ b/src/include/catalog/pg_user_mapping.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_user_mapping.h
+ *	  definition of the system "user mapping" relation (pg_user_mapping)
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_user_mapping.h,v 1.1 2008/12/19 16:25:19 petere Exp $
+ *
+ * NOTES
+ *	  the genbki.sh script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_USER_MAPPING_H
+#define PG_USER_MAPPING_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_user_mapping definition.  cpp turns this into
+ *		typedef struct FormData_pg_user_mapping
+ * ----------------
+ */
+#define UserMappingRelationId	1418
+
+CATALOG(pg_user_mapping,1418)
+{
+	Oid			umuser;			/* Id of the user, InvalidOid if PUBLIC is wanted */
+	Oid			umserver;	   	/* server of this mapping */
+
+	/*
+	 * VARIABLE LENGTH FIELDS start here.  These fields may be NULL, too.
+	 */
+
+	text		umoptions[1];	/* user mapping options */
+} FormData_pg_user_mapping;
+
+/* ----------------
+ *		Form_pg_user_mapping corresponds to a pointer to a tuple with
+ *		the format of pg_user_mapping relation.
+ * ----------------
+ */
+typedef FormData_pg_user_mapping *Form_pg_user_mapping;
+
+/* ----------------
+ *		compiler constants for pg_user_mapping
+ * ----------------
+ */
+
+#define Natts_pg_user_mapping				3
+#define Anum_pg_user_mapping_umuser			1
+#define Anum_pg_user_mapping_umserver		2
+#define Anum_pg_user_mapping_umoptions		3
+
+#endif   /* PG_USER_MAPPING_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 61a852623c35530613621ebf7f66d133109822cb..71f7e71522509d5938f02fa03237656a194b0d3c 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.90 2008/12/04 17:51:27 petere Exp $
+ * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.91 2008/12/19 16:25:19 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -118,6 +118,22 @@ extern void AlterTSConfigurationOwner(List *name, Oid newOwnerId);
 extern text *serialize_deflist(List *deflist);
 extern List *deserialize_deflist(Datum txt);
 
+/* commands/foreigncmds.c */
+extern void AlterForeignServerOwner(const char *name, Oid newOwnerId);
+extern void AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId);
+extern void CreateForeignDataWrapper(CreateFdwStmt *stmt);
+extern void AlterForeignDataWrapper(AlterFdwStmt *stmt);
+extern void RemoveForeignDataWrapper(DropFdwStmt *stmt);
+extern void RemoveForeignDataWrapperById(Oid fdwId);
+extern void CreateForeignServer(CreateForeignServerStmt *stmt);
+extern void AlterForeignServer(AlterForeignServerStmt *stmt);
+extern void RemoveForeignServer(DropForeignServerStmt *stmt);
+extern void RemoveForeignServerById(Oid srvId);
+extern void CreateUserMapping(CreateUserMappingStmt *stmt);
+extern void AlterUserMapping(AlterUserMappingStmt *stmt);
+extern void RemoveUserMapping(DropUserMappingStmt *stmt);
+extern void RemoveUserMappingById(Oid umId);
+
 /* support routines in commands/define.c */
 
 extern char *case_translate_language_name(const char *input);
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
new file mode 100644
index 0000000000000000000000000000000000000000..7fce532888ef6e1757c6b2cb7c8caefed7e6ac2f
--- /dev/null
+++ b/src/include/foreign/foreign.h
@@ -0,0 +1,101 @@
+/*-------------------------------------------------------------------------
+ *
+ * foreign.h
+ *	  support for foreign-data wrappers, servers and user mappings.
+ *
+ *
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/foreign/foreign.h,v 1.1 2008/12/19 16:25:19 petere Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FOREIGN_H
+#define FOREIGN_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+
+/* Helper for obtaining username for user mapping */
+#define MappingUserName(userid) \
+	(OidIsValid(userid) ? GetUserNameFromId(userid) : "public")
+
+
+/*
+ * Generic option types for validation.
+ * NB! Thes are treated as flags, so use only powers of two here.
+ */
+typedef enum {
+	InvalidOpt = 0,
+	ServerOpt = 1,				/* options applicable to SERVER */
+	UserMappingOpt = 2,			/* options for USER MAPPING */
+	FdwOpt = 4,					/* options for FOREIGN DATA WRAPPER */
+} GenericOptionFlags;
+
+typedef struct ForeignDataWrapperLibrary ForeignDataWrapperLibrary;
+
+typedef struct ForeignDataWrapper
+{
+	Oid		fdwid;				/* FDW Oid */
+	Oid		owner;				/* FDW owner user Oid */
+	char   *fdwname;			/* Name of the FDW */
+	char   *fdwlibrary;			/* Library name */
+	List   *options;			/* fdwoptions as DefElem list */
+
+	ForeignDataWrapperLibrary *lib;	/* interface to the FDW functions */
+} ForeignDataWrapper;
+
+typedef struct ForeignServer
+{
+	Oid		serverid;			/* server Oid */
+	Oid		fdwid;				/* foreign-data wrapper */
+	Oid		owner;				/* server owner user Oid */
+	char	*servername;		/* name of the server */
+	char	*servertype;		/* server type, optional */
+	char	*serverversion;		/* server version, optional */
+	List	*options;			/* srvoptions as DefElem list */
+} ForeignServer;
+
+typedef struct UserMapping
+{
+	Oid		userid;				/* local user Oid */
+	Oid		serverid;			/* server Oid */
+	List	*options;			/* useoptions as DefElem list */
+} UserMapping;
+
+
+/*
+ * Foreign-data wrapper library function types.
+ */
+typedef void (*OptionListValidatorFunc)(ForeignDataWrapper *,
+										GenericOptionFlags,
+										List *);
+
+/*
+ * Interface functions to the foreign-data wrapper. This is decoupled
+ * from the FDW as there maybe several FDW-s accessing the same library.
+ */
+struct ForeignDataWrapperLibrary
+{
+	char 	   *libname;		/* name of the library file */
+
+	OptionListValidatorFunc	validateOptionList;
+};
+
+
+extern ForeignServer *GetForeignServer(Oid serverid);
+extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
+extern Oid GetForeignServerOidByName(const char *name, bool missing_ok);
+extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
+extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
+extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
+													   bool missing_ok);
+extern Oid GetForeignDataWrapperOidByName(const char *name, bool missing_ok);
+extern ForeignDataWrapperLibrary *GetForeignDataWrapperLibrary(const char *libname);
+
+/* Foreign data wrapper interface functions */
+extern void _pg_validateOptionList(ForeignDataWrapper *fdw,
+								   GenericOptionFlags flags, List *options);
+
+#endif /* FOREIGN_H */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index d4e58cfe5f31391d11d6060d8a2f352daab5b4e7..a863c1369b2471c7b1bd13d7fa5f861408f86242 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/makefuncs.h,v 1.63 2008/09/01 20:42:45 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/makefuncs.h,v 1.64 2008/12/19 16:25:19 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -67,4 +67,6 @@ extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype,
 
 extern DefElem *makeDefElem(char *name, Node *arg);
 
+extern OptionDefElem *makeOptionDefElem(int op, DefElem *def);
+
 #endif   /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 4161c08ec54da18cb2a680669a06d867d737aa31..7383697f6ce1fda801f23eb7d8ca0c2c9cb40fb5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.215 2008/11/22 22:47:06 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.216 2008/12/19 16:25:19 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -325,6 +325,15 @@ typedef enum NodeTag
 	T_CreateEnumStmt,
 	T_AlterTSDictionaryStmt,
 	T_AlterTSConfigurationStmt,
+	T_CreateFdwStmt,
+	T_AlterFdwStmt,
+	T_DropFdwStmt,
+	T_CreateForeignServerStmt,
+	T_AlterForeignServerStmt,
+	T_DropForeignServerStmt,
+	T_CreateUserMappingStmt,
+	T_AlterUserMappingStmt,
+	T_DropUserMappingStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -348,6 +357,7 @@ typedef enum NodeTag
 	T_IndexElem,
 	T_Constraint,
 	T_DefElem,
+	T_OptionDefElem,
 	T_RangeTblEntry,
 	T_SortGroupClause,
 	T_FkConstraint,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index c84a77585fc4fa1bf9bd414b6f58d0450cefba3b..2f22475bf10fe4008eba75da59c37937aaba729b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -13,7 +13,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.383 2008/12/18 18:20:35 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.384 2008/12/19 16:25:19 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -49,6 +49,13 @@ typedef enum SortByNulls
 	SORTBY_NULLS_LAST
 } SortByNulls;
 
+/* Alter operations for generic options */
+typedef enum AlterOptionOp
+{
+	ALTER_OPT_DROP = -1,
+	ALTER_OPT_SET,
+	ALTER_OPT_ADD
+} AlterOptionOp;
 
 /*
  * Grantable rights are encoded so that we can OR them together in a bitmask.
@@ -67,7 +74,7 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
 #define ACL_REFERENCES	(1<<5)
 #define ACL_TRIGGER		(1<<6)
 #define ACL_EXECUTE		(1<<7)	/* for functions */
-#define ACL_USAGE		(1<<8)	/* for languages and namespaces */
+#define ACL_USAGE		(1<<8)	/* for languages, namespaces, FDWs, and servers */
 #define ACL_CREATE		(1<<9)	/* for namespaces and databases */
 #define ACL_CREATE_TEMP (1<<10) /* for databases */
 #define ACL_CONNECT		(1<<11) /* for databases */
@@ -466,6 +473,17 @@ typedef struct DefElem
 	Node	   *arg;			/* a (Value *) or a (TypeName *) */
 } DefElem;
 
+/*
+ * Option definition. Used in options definition lists, with optional alter
+ * operation.
+ */
+typedef struct OptionDefElem
+{
+	NodeTag			type;
+	AlterOptionOp	alter_op;		/* Alter operation: ADD/SET/DROP */
+	DefElem		   *def;			/* The actual definition */
+} OptionDefElem;
+
 /*
  * LockingClause - raw representation of FOR UPDATE/SHARE options
  *
@@ -930,6 +948,8 @@ typedef enum ObjectType
 	OBJECT_CONVERSION,
 	OBJECT_DATABASE,
 	OBJECT_DOMAIN,
+	OBJECT_FDW,
+	OBJECT_FOREIGN_SERVER,
 	OBJECT_FUNCTION,
 	OBJECT_INDEX,
 	OBJECT_LANGUAGE,
@@ -1076,6 +1096,8 @@ typedef enum GrantObjectType
 	ACL_OBJECT_RELATION,		/* table, view */
 	ACL_OBJECT_SEQUENCE,		/* sequence */
 	ACL_OBJECT_DATABASE,		/* database */
+	ACL_OBJECT_FDW,				/* foreign-data wrapper */
+	ACL_OBJECT_FOREIGN_SERVER,	/* foreign server */
 	ACL_OBJECT_FUNCTION,		/* function */
 	ACL_OBJECT_LANGUAGE,		/* procedural language */
 	ACL_OBJECT_NAMESPACE,		/* namespace */
@@ -1328,6 +1350,96 @@ typedef struct DropTableSpaceStmt
 	bool		missing_ok;		/* skip error if missing? */
 } DropTableSpaceStmt;
 
+/* ----------------------
+ *		Create/Drop FOREIGN DATA WRAPPER Statements
+ * ----------------------
+ */
+
+typedef struct CreateFdwStmt
+{
+	NodeTag		type;
+	char	   *fdwname;		/* foreign-data wrapper name */
+	char	   *library;		/* libray name */
+	List	   *options;		/* generic options to FDW */
+} CreateFdwStmt;
+
+typedef struct AlterFdwStmt
+{
+	NodeTag		type;
+	char	   *fdwname;		/* foreign-data wrapper name */
+	char	   *library;		/* libray name */
+	List	   *options;		/* generic options to FDW */
+} AlterFdwStmt;
+
+typedef struct DropFdwStmt
+{
+	NodeTag		type;
+	char	   *fdwname;		/* foreign-data wrapper name */
+	bool		missing_ok;		/* don't complain if missing */
+	DropBehavior behavior;		/* drop behavior - cascade/restrict */
+} DropFdwStmt;
+
+/* ----------------------
+ *		Create/Drop FOREIGN SERVER Statements
+ * ----------------------
+ */
+
+typedef struct CreateForeignServerStmt
+{
+	NodeTag		type;
+	char	   *servername;		/* server name */
+	char	   *servertype;		/* optional server type */
+	char	   *version;		/* optional server version */
+	char	   *fdwname;		/* FDW name */
+	List	   *options;		/* generic options to server */
+} CreateForeignServerStmt;
+
+typedef struct AlterForeignServerStmt
+{
+	NodeTag		type;
+	char	   *servername;		/* server name */
+	char	   *version;		/* optional server version */
+	List	   *options;		/* generic options to server */
+	bool		has_version;	/* version specified */
+} AlterForeignServerStmt;
+
+typedef struct DropForeignServerStmt
+{
+	NodeTag		type;
+	char	   *servername;		/* server name */
+	bool		missing_ok;		/* ignore missing servers */
+	DropBehavior behavior;		/* drop behavior - cascade/restrict */
+} DropForeignServerStmt;
+
+/* ----------------------
+ *		Create/Drop USER MAPPING Statements
+ * ----------------------
+ */
+
+typedef struct CreateUserMappingStmt
+{
+	NodeTag		type;
+	char	   *username;		/* username or PUBLIC/CURRENT_USER */
+	char	   *servername;		/* server name */
+	List	   *options;		/* generic options to server */
+} CreateUserMappingStmt;
+
+typedef struct AlterUserMappingStmt
+{
+	NodeTag		type;
+	char	   *username;		/* username or PUBLIC/CURRENT_USER */
+	char	   *servername;		/* server name */
+	List	   *options;		/* generic options to server */
+} AlterUserMappingStmt;
+
+typedef struct DropUserMappingStmt
+{
+	NodeTag		type;
+	char	   *username;		/* username or PUBLIC/CURRENT_USER */
+	char	   *servername;		/* server name */
+	bool		missing_ok;		/* ignore missing mappings */
+} DropUserMappingStmt;
+
 /* ----------------------
  *		Create/Drop TRIGGER Statements
  * ----------------------
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 553ceabee3bdd8ab3fed151228f6c4e7df973d32..defbb47eab106de22e55e0a5177f96d5b390750d 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.104 2008/09/08 00:47:41 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.105 2008/12/19 16:25:19 petere Exp $
  *
  * NOTES
  *	  An ACL array is simply an array of AclItems, representing the union
@@ -146,6 +146,8 @@ typedef ArrayType Acl;
 #define ACL_ALL_RIGHTS_RELATION		(ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_TRUNCATE|ACL_REFERENCES|ACL_TRIGGER)
 #define ACL_ALL_RIGHTS_SEQUENCE		(ACL_USAGE|ACL_SELECT|ACL_UPDATE)
 #define ACL_ALL_RIGHTS_DATABASE		(ACL_CREATE|ACL_CREATE_TEMP|ACL_CONNECT)
+#define ACL_ALL_RIGHTS_FDW			(ACL_USAGE)
+#define ACL_ALL_RIGHTS_FOREIGN_SERVER (ACL_USAGE)
 #define ACL_ALL_RIGHTS_FUNCTION		(ACL_EXECUTE)
 #define ACL_ALL_RIGHTS_LANGUAGE		(ACL_USAGE)
 #define ACL_ALL_RIGHTS_NAMESPACE	(ACL_USAGE|ACL_CREATE)
@@ -184,6 +186,8 @@ typedef enum AclObjectKind
 	ACL_KIND_TABLESPACE,		/* pg_tablespace */
 	ACL_KIND_TSDICTIONARY,		/* pg_ts_dict */
 	ACL_KIND_TSCONFIGURATION,	/* pg_ts_config */
+	ACL_KIND_FDW,				/* pg_foreign_data_wrapper */
+	ACL_KIND_FOREIGN_SERVER,	/* pg_foreign_server */
 	MAX_ACL_KIND				/* MUST BE LAST */
 } AclObjectKind;
 
@@ -261,6 +265,10 @@ extern AclMode pg_namespace_aclmask(Oid nsp_oid, Oid roleid,
 					 AclMode mask, AclMaskHow how);
 extern AclMode pg_tablespace_aclmask(Oid spc_oid, Oid roleid,
 					  AclMode mask, AclMaskHow how);
+extern AclMode pg_foreign_data_wrapper_aclmask(Oid fdw_oid, Oid roleid,
+					  AclMode mask, AclMaskHow how);
+extern AclMode pg_foreign_server_aclmask(Oid srv_oid, Oid roleid,
+					  AclMode mask, AclMaskHow how);
 
 extern AclResult pg_class_aclcheck(Oid table_oid, Oid roleid, AclMode mode);
 extern AclResult pg_database_aclcheck(Oid db_oid, Oid roleid, AclMode mode);
@@ -268,6 +276,8 @@ extern AclResult pg_proc_aclcheck(Oid proc_oid, Oid roleid, AclMode mode);
 extern AclResult pg_language_aclcheck(Oid lang_oid, Oid roleid, AclMode mode);
 extern AclResult pg_namespace_aclcheck(Oid nsp_oid, Oid roleid, AclMode mode);
 extern AclResult pg_tablespace_aclcheck(Oid spc_oid, Oid roleid, AclMode mode);
+extern AclResult pg_foreign_data_wrapper_aclcheck(Oid fdw_oid, Oid roleid, AclMode mode);
+extern AclResult pg_foreign_server_aclcheck(Oid srv_oid, Oid roleid, AclMode mode);
 
 extern void aclcheck_error(AclResult aclerr, AclObjectKind objectkind,
 			   const char *objectname);
@@ -286,5 +296,6 @@ extern bool pg_database_ownercheck(Oid db_oid, Oid roleid);
 extern bool pg_conversion_ownercheck(Oid conv_oid, Oid roleid);
 extern bool pg_ts_dict_ownercheck(Oid dict_oid, Oid roleid);
 extern bool pg_ts_config_ownercheck(Oid cfg_oid, Oid roleid);
+extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
 
 #endif   /* ACL_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 7f20cdf0015323b1b8e0e0522d86a3af49bf9b95..a00415aaa65d41f84f38385dc6032b5b02691e88 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.327 2008/12/04 17:51:28 petere Exp $
+ * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.328 2008/12/19 16:25:19 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -34,6 +34,12 @@ extern Datum has_database_privilege_id_name(PG_FUNCTION_ARGS);
 extern Datum has_database_privilege_id_id(PG_FUNCTION_ARGS);
 extern Datum has_database_privilege_name(PG_FUNCTION_ARGS);
 extern Datum has_database_privilege_id(PG_FUNCTION_ARGS);
+extern Datum has_foreign_data_wrapper_privilege_name_name(PG_FUNCTION_ARGS);
+extern Datum has_foreign_data_wrapper_privilege_name_id(PG_FUNCTION_ARGS);
+extern Datum has_foreign_data_wrapper_privilege_id_name(PG_FUNCTION_ARGS);
+extern Datum has_foreign_data_wrapper_privilege_id_id(PG_FUNCTION_ARGS);
+extern Datum has_foreign_data_wrapper_privilege_name(PG_FUNCTION_ARGS);
+extern Datum has_foreign_data_wrapper_privilege_id(PG_FUNCTION_ARGS);
 extern Datum has_function_privilege_name_name(PG_FUNCTION_ARGS);
 extern Datum has_function_privilege_name_id(PG_FUNCTION_ARGS);
 extern Datum has_function_privilege_id_name(PG_FUNCTION_ARGS);
@@ -52,6 +58,12 @@ extern Datum has_schema_privilege_id_name(PG_FUNCTION_ARGS);
 extern Datum has_schema_privilege_id_id(PG_FUNCTION_ARGS);
 extern Datum has_schema_privilege_name(PG_FUNCTION_ARGS);
 extern Datum has_schema_privilege_id(PG_FUNCTION_ARGS);
+extern Datum has_server_privilege_name_name(PG_FUNCTION_ARGS);
+extern Datum has_server_privilege_name_id(PG_FUNCTION_ARGS);
+extern Datum has_server_privilege_id_name(PG_FUNCTION_ARGS);
+extern Datum has_server_privilege_id_id(PG_FUNCTION_ARGS);
+extern Datum has_server_privilege_name(PG_FUNCTION_ARGS);
+extern Datum has_server_privilege_id(PG_FUNCTION_ARGS);
 extern Datum has_tablespace_privilege_name_name(PG_FUNCTION_ARGS);
 extern Datum has_tablespace_privilege_name_id(PG_FUNCTION_ARGS);
 extern Datum has_tablespace_privilege_id_name(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index e6220477728218bb3aa01dfffde80a8fce932ba7..ded7a1d0523683db33e5fbc037a775b6aea1179e 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/syscache.h,v 1.72 2008/05/07 01:04:49 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/syscache.h,v 1.73 2008/12/19 16:25:19 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -51,6 +51,10 @@ enum SysCacheIdentifier
 	DATABASEOID,
 	ENUMOID,
 	ENUMTYPOIDNAME,
+	FOREIGNDATAWRAPPERNAME,
+	FOREIGNDATAWRAPPEROID,
+	FOREIGNSERVERNAME,
+	FOREIGNSERVEROID,
 	INDEXRELID,
 	LANGNAME,
 	LANGOID,
@@ -76,7 +80,9 @@ enum SysCacheIdentifier
 	TSTEMPLATENAMENSP,
 	TSTEMPLATEOID,
 	TYPENAMENSP,
-	TYPEOID
+	TYPEOID,
+	USERMAPPINGOID,
+	USERMAPPINGUSERSERVER
 };
 
 extern void InitCatalogCache(void);
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
new file mode 100644
index 0000000000000000000000000000000000000000..1d225eba14886e3928252b6e62b1e3a604ed4d16
--- /dev/null
+++ b/src/test/regress/expected/foreign_data.out
@@ -0,0 +1,1008 @@
+--
+-- Test foreign-data wrapper and server management.
+--
+-- Clean up in case a prior regression run failed
+-- Suppress NOTICE messages when roles don't exist
+SET client_min_messages TO 'error';
+DROP ROLE IF EXISTS foreign_data_user, regress_test_role, regress_test_role2, regress_test_role_super, regress_test_indirect, unpriviled_role;
+RESET client_min_messages;
+CREATE ROLE foreign_data_user LOGIN SUPERUSER;
+SET SESSION AUTHORIZATION 'foreign_data_user';
+CREATE ROLE regress_test_role;
+CREATE ROLE regress_test_role2;
+CREATE ROLE regress_test_role_super SUPERUSER;
+CREATE ROLE regress_test_indirect;
+CREATE ROLE unprivileged_role;
+CREATE FOREIGN DATA WRAPPER dummy LIBRARY 'dummy_fdw' LANGUAGE C;
+CREATE FOREIGN DATA WRAPPER postgresql LIBRARY 'postgresql_fdw' LANGUAGE C;
+-- At this point we should have 2 built-in wrappers and no servers.
+SELECT fdwname, fdwlibrary, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3;
+  fdwname   |   fdwlibrary   | fdwoptions 
+------------+----------------+------------
+ dummy      | dummy_fdw      | 
+ postgresql | postgresql_fdw | 
+(2 rows)
+
+SELECT srvname, srvoptions FROM pg_foreign_server;
+ srvname | srvoptions 
+---------+------------
+(0 rows)
+
+SELECT * FROM pg_user_mapping;
+ umuser | umserver | umoptions 
+--------+----------+-----------
+(0 rows)
+
+-- CREATE FOREIGN DATA WRAPPER
+CREATE FOREIGN DATA WRAPPER foo LIBRARY '' LANGUAGE C;            -- ERROR
+ERROR:  could not access file "": No such file or directory
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'plpgsql' LANGUAGE C;
+DROP FOREIGN DATA WRAPPER foo;
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C;
+\dew
+          List of foreign-data wrappers
+    Name    |       Owner       |    Library     
+------------+-------------------+----------------
+ dummy      | foreign_data_user | dummy_fdw
+ foo        | foreign_data_user | dummy_fdw
+ postgresql | foreign_data_user | postgresql_fdw
+(3 rows)
+
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C; -- duplicate
+ERROR:  foreign-data wrapper "foo" already exists
+CREATE FOREIGN DATA WRAPPER "Foo" LIBRARY 'dummy_fdw' LANGUAGE C;
+DROP FOREIGN DATA WRAPPER "Foo";
+DROP FOREIGN DATA WRAPPER foo;
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C OPTIONS (testing '1');
+\dew+
+                           List of foreign-data wrappers
+    Name    |       Owner       |    Library     | Access privileges |   Options   
+------------+-------------------+----------------+-------------------+-------------
+ dummy      | foreign_data_user | dummy_fdw      |                   | 
+ foo        | foreign_data_user | dummy_fdw      |                   | {testing=1}
+ postgresql | foreign_data_user | postgresql_fdw |                   | 
+(3 rows)
+
+DROP FOREIGN DATA WRAPPER foo;
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C OPTIONS (testing '1', testing '2');   -- ERROR
+ERROR:  option "testing" provided more than once
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C OPTIONS (testing '1', another '2');
+\dew+
+                                List of foreign-data wrappers
+    Name    |       Owner       |    Library     | Access privileges |        Options        
+------------+-------------------+----------------+-------------------+-----------------------
+ dummy      | foreign_data_user | dummy_fdw      |                   | 
+ foo        | foreign_data_user | dummy_fdw      |                   | {testing=1,another=2}
+ postgresql | foreign_data_user | postgresql_fdw |                   | 
+(3 rows)
+
+DROP FOREIGN DATA WRAPPER foo;
+SET ROLE regress_test_role;
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C; -- ERROR
+ERROR:  permission denied to create foreign-data wrapper "foo"
+HINT:  Must be superuser to create a foreign-data wrapper.
+RESET ROLE;
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'postgresql_fdw' LANGUAGE C;
+\dew+
+                         List of foreign-data wrappers
+    Name    |       Owner       |    Library     | Access privileges | Options 
+------------+-------------------+----------------+-------------------+---------
+ dummy      | foreign_data_user | dummy_fdw      |                   | 
+ foo        | foreign_data_user | postgresql_fdw |                   | 
+ postgresql | foreign_data_user | postgresql_fdw |                   | 
+(3 rows)
+
+-- ALTER FOREIGN DATA WRAPPER
+ALTER FOREIGN DATA WRAPPER foo LIBRARY '';                  -- ERROR
+ERROR:  could not access file "": No such file or directory
+ALTER FOREIGN DATA WRAPPER foo LIBRARY 'plpgsql';
+WARNING:  changing the foreign-data wrapper library can cause the options for dependent objects to become invalid
+ALTER FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw';
+WARNING:  changing the foreign-data wrapper library can cause the options for dependent objects to become invalid
+\dew+
+                         List of foreign-data wrappers
+    Name    |       Owner       |    Library     | Access privileges | Options 
+------------+-------------------+----------------+-------------------+---------
+ dummy      | foreign_data_user | dummy_fdw      |                   | 
+ foo        | foreign_data_user | dummy_fdw      |                   | 
+ postgresql | foreign_data_user | postgresql_fdw |                   | 
+(3 rows)
+
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '1', b '2');
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (SET c '4');         -- ERROR
+ERROR:  option "c" not found
+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       |    Library     | Access privileges |  Options  
+------------+-------------------+----------------+-------------------+-----------
+ dummy      | foreign_data_user | dummy_fdw      |                   | 
+ foo        | foreign_data_user | dummy_fdw      |                   | {a=1,b=2}
+ postgresql | foreign_data_user | postgresql_fdw |                   | 
+(3 rows)
+
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (DROP a, SET b '3', ADD c '4');
+\dew+
+                          List of foreign-data wrappers
+    Name    |       Owner       |    Library     | Access privileges |  Options  
+------------+-------------------+----------------+-------------------+-----------
+ dummy      | foreign_data_user | dummy_fdw      |                   | 
+ foo        | foreign_data_user | dummy_fdw      |                   | {b=3,c=4}
+ postgresql | foreign_data_user | postgresql_fdw |                   | 
+(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       |    Library     | Access privileges |    Options    
+------------+-------------------+----------------+-------------------+---------------
+ dummy      | foreign_data_user | dummy_fdw      |                   | 
+ foo        | foreign_data_user | dummy_fdw      |                   | {b=3,c=4,a=2}
+ postgresql | foreign_data_user | postgresql_fdw |                   | 
+(3 rows)
+
+SET ROLE regress_test_role;
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD d '5');         -- ERROR
+ERROR:  permission denied to alter foreign-data wrapper "foo"
+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       |    Library     | Access privileges |      Options      
+------------+-------------------+----------------+-------------------+-------------------
+ dummy      | foreign_data_user | dummy_fdw      |                   | 
+ foo        | foreign_data_user | dummy_fdw      |                   | {b=3,c=4,a=2,d=5}
+ postgresql | foreign_data_user | postgresql_fdw |                   | 
+(3 rows)
+
+ALTER FOREIGN DATA WRAPPER foo OWNER TO regress_test_role;  -- ERROR
+ERROR:  permission denied to change owner of foreign-data wrapper "foo"
+HINT:  The owner of a foreign-data wrapper must be a superuser.
+ALTER FOREIGN DATA WRAPPER foo OWNER TO regress_test_role_super;
+ALTER ROLE regress_test_role_super NOSUPERUSER;
+SET ROLE regress_test_role_super;
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD e '6');         -- ERROR
+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          |    Library     | Access privileges |      Options      
+------------+-------------------------+----------------+-------------------+-------------------
+ dummy      | foreign_data_user       | dummy_fdw      |                   | 
+ foo        | regress_test_role_super | dummy_fdw      |                   | {b=3,c=4,a=2,d=5}
+ postgresql | foreign_data_user       | postgresql_fdw |                   | 
+(3 rows)
+
+-- DROP FOREIGN DATA WRAPPER
+DROP FOREIGN DATA WRAPPER nonexistent;                      -- ERROR
+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          |    Library     | Access privileges |      Options      
+------------+-------------------------+----------------+-------------------+-------------------
+ dummy      | foreign_data_user       | dummy_fdw      |                   | 
+ foo        | regress_test_role_super | dummy_fdw      |                   | {b=3,c=4,a=2,d=5}
+ postgresql | foreign_data_user       | postgresql_fdw |                   | 
+(3 rows)
+
+DROP ROLE regress_test_role_super;                          -- ERROR
+ERROR:  role "regress_test_role_super" cannot be dropped because some objects depend on it
+DETAIL:  owner of foreign-data wrapper foo
+SET ROLE regress_test_role_super;
+DROP FOREIGN DATA WRAPPER foo;                              -- ERROR
+ERROR:  permission denied to drop foreign-data wrapper "foo"
+HINT:  Must be superuser to drop a foreign-data wrapper.
+RESET ROLE;
+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       |    Library     | Access privileges | Options 
+------------+-------------------+----------------+-------------------+---------
+ dummy      | foreign_data_user | dummy_fdw      |                   | 
+ postgresql | foreign_data_user | postgresql_fdw |                   | 
+(2 rows)
+
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C;
+CREATE SERVER s1 FOREIGN DATA WRAPPER foo;
+CREATE USER MAPPING FOR current_user SERVER s1;
+\dew+
+                         List of foreign-data wrappers
+    Name    |       Owner       |    Library     | Access privileges | Options 
+------------+-------------------+----------------+-------------------+---------
+ dummy      | foreign_data_user | dummy_fdw      |                   | 
+ foo        | foreign_data_user | dummy_fdw      |                   | 
+ postgresql | foreign_data_user | postgresql_fdw |                   | 
+(3 rows)
+
+\des+
+                                    List of foreign servers
+ Name |       Owner       | Foreign-data wrapper | Access privileges | Type | Version | Options 
+------+-------------------+----------------------+-------------------+------+---------+---------
+ s1   | foreign_data_user | foo                  |                   |      |         | 
+(1 row)
+
+\deu+
+        List of user mappings
+ Server |     Username      | Options 
+--------+-------------------+---------
+ s1     | foreign_data_user | 
+(1 row)
+
+DROP FOREIGN DATA WRAPPER foo;                              -- ERROR
+ERROR:  cannot drop foreign-data wrapper foo because other objects depend on it
+DETAIL:  server s1 depends on foreign-data wrapper foo
+user mapping for foreign_data_user depends on server s1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+SET ROLE regress_test_role;
+DROP FOREIGN DATA WRAPPER foo CASCADE;                      -- ERROR
+ERROR:  permission denied to drop foreign-data wrapper "foo"
+HINT:  Must be superuser to drop a foreign-data wrapper.
+RESET ROLE;
+DROP FOREIGN DATA WRAPPER foo CASCADE;
+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       |    Library     | Access privileges | Options 
+------------+-------------------+----------------+-------------------+---------
+ dummy      | foreign_data_user | dummy_fdw      |                   | 
+ postgresql | foreign_data_user | postgresql_fdw |                   | 
+(2 rows)
+
+\des+
+                              List of foreign servers
+ Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | Options 
+------+-------+----------------------+-------------------+------+---------+---------
+(0 rows)
+
+\deu+
+    List of user mappings
+ Server | Username | Options 
+--------+----------+---------
+(0 rows)
+
+-- exercise CREATE SERVER
+CREATE SERVER s1 FOREIGN DATA WRAPPER foo;                  -- ERROR
+ERROR:  foreign-data wrapper "foo" does not exist
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C OPTIONS (test_wrapper 'true');
+CREATE SERVER s1 FOREIGN DATA WRAPPER foo;
+CREATE SERVER s1 FOREIGN DATA WRAPPER foo;                  -- ERROR
+ERROR:  server "s1" already exists
+CREATE SERVER s2 FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbname 'b');
+CREATE SERVER s3 TYPE 'oracle' FOREIGN DATA WRAPPER foo;
+CREATE SERVER s4 TYPE 'oracle' FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbname 'b');
+CREATE SERVER s5 VERSION '15.0' FOREIGN DATA WRAPPER foo;
+CREATE SERVER s6 VERSION '16.0' FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbname 'b');
+CREATE SERVER "S6" FOREIGN DATA WRAPPER foo OPTIONS (mixed_case_names 'true');
+CREATE SERVER s7 TYPE 'oracle' VERSION '17.0' FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbname 'b');
+CREATE SERVER s8 FOREIGN DATA WRAPPER postgresql OPTIONS (foo '1'); -- ERROR
+ERROR:  invalid option "foo" to server
+HINT:  valid server options are: authtype, service, connect_timeout, dbname, host, hostaddr, port, tty, options, requiressl, sslmode, gsslib
+CREATE SERVER s8 FOREIGN DATA WRAPPER postgresql OPTIONS (host 'localhost', dbname 's8db');
+\des+
+                                                List of foreign servers
+ Name |       Owner       | Foreign-data wrapper | Access privileges |  Type  | Version |           Options            
+------+-------------------+----------------------+-------------------+--------+---------+------------------------------
+ S6   | foreign_data_user | foo                  |                   |        |         | {mixed_case_names=true}
+ s1   | foreign_data_user | foo                  |                   |        |         | 
+ s2   | foreign_data_user | foo                  |                   |        |         | {host=a,dbname=b}
+ s3   | foreign_data_user | foo                  |                   | oracle |         | 
+ s4   | foreign_data_user | foo                  |                   | oracle |         | {host=a,dbname=b}
+ s5   | foreign_data_user | foo                  |                   |        | 15.0    | 
+ s6   | foreign_data_user | foo                  |                   |        | 16.0    | {host=a,dbname=b}
+ s7   | foreign_data_user | foo                  |                   | oracle | 17.0    | {host=a,dbname=b}
+ s8   | foreign_data_user | postgresql           |                   |        |         | {host=localhost,dbname=s8db}
+(9 rows)
+
+SET ROLE regress_test_role;
+CREATE SERVER st1 FOREIGN DATA WRAPPER foo;                 -- ERROR: no usage on FDW
+ERROR:  permission denied for foreign-data wrapper foo
+RESET ROLE;
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role;
+SET ROLE regress_test_role;
+CREATE SERVER st1 FOREIGN DATA WRAPPER foo;
+RESET ROLE;
+\des+
+                                                List of foreign servers
+ Name |       Owner       | Foreign-data wrapper | Access privileges |  Type  | Version |           Options            
+------+-------------------+----------------------+-------------------+--------+---------+------------------------------
+ S6   | foreign_data_user | foo                  |                   |        |         | {mixed_case_names=true}
+ s1   | foreign_data_user | foo                  |                   |        |         | 
+ s2   | foreign_data_user | foo                  |                   |        |         | {host=a,dbname=b}
+ s3   | foreign_data_user | foo                  |                   | oracle |         | 
+ s4   | foreign_data_user | foo                  |                   | oracle |         | {host=a,dbname=b}
+ s5   | foreign_data_user | foo                  |                   |        | 15.0    | 
+ s6   | foreign_data_user | foo                  |                   |        | 16.0    | {host=a,dbname=b}
+ s7   | foreign_data_user | foo                  |                   | oracle | 17.0    | {host=a,dbname=b}
+ s8   | foreign_data_user | postgresql           |                   |        |         | {host=localhost,dbname=s8db}
+ st1  | regress_test_role | foo                  |                   |        |         | 
+(10 rows)
+
+REVOKE USAGE ON FOREIGN DATA WRAPPER foo FROM regress_test_role;
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_indirect;
+SET ROLE regress_test_role;
+CREATE SERVER st2 FOREIGN DATA WRAPPER foo;                 -- ERROR
+ERROR:  permission denied for foreign-data wrapper foo
+RESET ROLE;
+GRANT regress_test_indirect TO regress_test_role;
+SET ROLE regress_test_role;
+CREATE SERVER st2 FOREIGN DATA WRAPPER foo;
+\des+
+                                                List of foreign servers
+ Name |       Owner       | Foreign-data wrapper | Access privileges |  Type  | Version |           Options            
+------+-------------------+----------------------+-------------------+--------+---------+------------------------------
+ S6   | foreign_data_user | foo                  |                   |        |         | {mixed_case_names=true}
+ s1   | foreign_data_user | foo                  |                   |        |         | 
+ s2   | foreign_data_user | foo                  |                   |        |         | {host=a,dbname=b}
+ s3   | foreign_data_user | foo                  |                   | oracle |         | 
+ s4   | foreign_data_user | foo                  |                   | oracle |         | {host=a,dbname=b}
+ s5   | foreign_data_user | foo                  |                   |        | 15.0    | 
+ s6   | foreign_data_user | foo                  |                   |        | 16.0    | {host=a,dbname=b}
+ s7   | foreign_data_user | foo                  |                   | oracle | 17.0    | {host=a,dbname=b}
+ s8   | foreign_data_user | postgresql           |                   |        |         | {host=localhost,dbname=s8db}
+ st1  | regress_test_role | foo                  |                   |        |         | 
+ st2  | regress_test_role | foo                  |                   |        |         | 
+(11 rows)
+
+RESET ROLE;
+REVOKE regress_test_indirect FROM regress_test_role;
+-- ALTER SERVER
+ALTER SERVER s0;                                            -- ERROR
+ERROR:  syntax error at or near ";"
+LINE 1: ALTER SERVER s0;
+                       ^
+ALTER SERVER s0 OPTIONS (a '1');                            -- ERROR
+ERROR:  server "s0" does not exist
+ALTER SERVER s1 VERSION '1.0' OPTIONS (servername 's1');
+ALTER SERVER s2 VERSION '1.1';
+ALTER SERVER s3 OPTIONS (tnsname 'orcl', port '1521');
+GRANT USAGE ON FOREIGN SERVER s1 TO regress_test_role;
+GRANT USAGE ON FOREIGN SERVER s6 TO regress_test_role2 WITH GRANT OPTION;
+\des+
+                                                                               List of foreign servers
+ Name |       Owner       | Foreign-data wrapper |                                Access privileges                                |  Type  | Version |           Options            
+------+-------------------+----------------------+---------------------------------------------------------------------------------+--------+---------+------------------------------
+ S6   | foreign_data_user | foo                  |                                                                                 |        |         | {mixed_case_names=true}
+ s1   | foreign_data_user | foo                  | {foreign_data_user=U/foreign_data_user,regress_test_role=U/foreign_data_user}   |        | 1.0     | {servername=s1}
+ s2   | foreign_data_user | foo                  |                                                                                 |        | 1.1     | {host=a,dbname=b}
+ s3   | foreign_data_user | foo                  |                                                                                 | oracle |         | {tnsname=orcl,port=1521}
+ s4   | foreign_data_user | foo                  |                                                                                 | oracle |         | {host=a,dbname=b}
+ s5   | foreign_data_user | foo                  |                                                                                 |        | 15.0    | 
+ s6   | foreign_data_user | foo                  | {foreign_data_user=U/foreign_data_user,regress_test_role2=U*/foreign_data_user} |        | 16.0    | {host=a,dbname=b}
+ s7   | foreign_data_user | foo                  |                                                                                 | oracle | 17.0    | {host=a,dbname=b}
+ s8   | foreign_data_user | postgresql           |                                                                                 |        |         | {host=localhost,dbname=s8db}
+ st1  | regress_test_role | foo                  |                                                                                 |        |         | 
+ st2  | regress_test_role | foo                  |                                                                                 |        |         | 
+(11 rows)
+
+SET ROLE regress_test_role;
+ALTER SERVER s1 VERSION '1.1';                              -- ERROR
+ERROR:  must be owner of foreign server s1
+ALTER SERVER s1 OWNER TO regress_test_role;                 -- ERROR
+ERROR:  must be owner of foreign server s1
+RESET ROLE;
+ALTER SERVER s1 OWNER TO regress_test_role;
+GRANT regress_test_role2 TO regress_test_role;
+SET ROLE regress_test_role;
+ALTER SERVER s1 VERSION '1.1';
+ALTER SERVER s1 OWNER TO regress_test_role2;                -- ERROR
+ERROR:  permission denied for foreign-data wrapper foo
+RESET ROLE;
+ALTER SERVER s8 OPTIONS (foo '1');                          -- ERROR option validation
+ERROR:  invalid option "foo" to server
+HINT:  valid server options are: authtype, service, connect_timeout, dbname, host, hostaddr, port, tty, options, requiressl, sslmode, gsslib
+ALTER SERVER s8 OPTIONS (connect_timeout '30', SET dbname 'db1', DROP host);
+SET ROLE regress_test_role;
+ALTER SERVER s1 OWNER TO regress_test_indirect;             -- ERROR
+ERROR:  must be member of role "regress_test_indirect"
+RESET ROLE;
+GRANT regress_test_indirect TO regress_test_role;
+SET ROLE regress_test_role;
+ALTER SERVER s1 OWNER TO regress_test_indirect;
+RESET ROLE;
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_indirect;
+SET ROLE regress_test_role;
+ALTER SERVER s1 OWNER TO regress_test_indirect;
+RESET ROLE;
+DROP ROLE regress_test_indirect;                            -- ERROR
+ERROR:  role "regress_test_indirect" cannot be dropped because some objects depend on it
+DETAIL:  owner of server s1
+access to foreign-data wrapper foo
+\des+
+                                                                                  List of foreign servers
+ Name |         Owner         | Foreign-data wrapper |                                Access privileges                                |  Type  | Version |             Options             
+------+-----------------------+----------------------+---------------------------------------------------------------------------------+--------+---------+---------------------------------
+ S6   | foreign_data_user     | foo                  |                                                                                 |        |         | {mixed_case_names=true}
+ s1   | regress_test_indirect | foo                  | {foreign_data_user=U/foreign_data_user,regress_test_role=U/foreign_data_user}   |        | 1.1     | {servername=s1}
+ s2   | foreign_data_user     | foo                  |                                                                                 |        | 1.1     | {host=a,dbname=b}
+ s3   | foreign_data_user     | foo                  |                                                                                 | oracle |         | {tnsname=orcl,port=1521}
+ s4   | foreign_data_user     | foo                  |                                                                                 | oracle |         | {host=a,dbname=b}
+ s5   | foreign_data_user     | foo                  |                                                                                 |        | 15.0    | 
+ s6   | foreign_data_user     | foo                  | {foreign_data_user=U/foreign_data_user,regress_test_role2=U*/foreign_data_user} |        | 16.0    | {host=a,dbname=b}
+ s7   | foreign_data_user     | foo                  |                                                                                 | oracle | 17.0    | {host=a,dbname=b}
+ s8   | foreign_data_user     | postgresql           |                                                                                 |        |         | {dbname=db1,connect_timeout=30}
+ st1  | regress_test_role     | foo                  |                                                                                 |        |         | 
+ st2  | regress_test_role     | foo                  |                                                                                 |        |         | 
+(11 rows)
+
+-- DROP SERVER
+DROP SERVER nonexistent;                                    -- ERROR
+ERROR:  server "nonexistent" does not exist
+DROP SERVER IF EXISTS nonexistent;
+NOTICE:  server "nonexistent" does not exist, skipping
+\des
+               List of foreign servers
+ Name |         Owner         | Foreign-data wrapper 
+------+-----------------------+----------------------
+ S6   | foreign_data_user     | foo
+ s1   | regress_test_indirect | foo
+ s2   | foreign_data_user     | foo
+ s3   | foreign_data_user     | foo
+ s4   | foreign_data_user     | foo
+ s5   | foreign_data_user     | foo
+ s6   | foreign_data_user     | foo
+ s7   | foreign_data_user     | foo
+ s8   | foreign_data_user     | postgresql
+ st1  | regress_test_role     | foo
+ st2  | regress_test_role     | foo
+(11 rows)
+
+SET ROLE regress_test_role;
+DROP SERVER s2;                                             -- ERROR
+ERROR:  must be owner of foreign server s2
+DROP SERVER s1;
+RESET ROLE;
+\des
+             List of foreign servers
+ Name |       Owner       | Foreign-data wrapper 
+------+-------------------+----------------------
+ S6   | foreign_data_user | foo
+ s2   | foreign_data_user | foo
+ s3   | foreign_data_user | foo
+ s4   | foreign_data_user | foo
+ s5   | foreign_data_user | foo
+ s6   | foreign_data_user | foo
+ s7   | foreign_data_user | foo
+ s8   | foreign_data_user | postgresql
+ st1  | regress_test_role | foo
+ st2  | regress_test_role | foo
+(10 rows)
+
+ALTER SERVER s2 OWNER TO regress_test_role;
+SET ROLE regress_test_role;
+DROP SERVER s2;
+RESET ROLE;
+\des
+             List of foreign servers
+ Name |       Owner       | Foreign-data wrapper 
+------+-------------------+----------------------
+ S6   | foreign_data_user | foo
+ s3   | foreign_data_user | foo
+ s4   | foreign_data_user | foo
+ s5   | foreign_data_user | foo
+ s6   | foreign_data_user | foo
+ s7   | foreign_data_user | foo
+ s8   | foreign_data_user | postgresql
+ st1  | regress_test_role | foo
+ st2  | regress_test_role | foo
+(9 rows)
+
+CREATE USER MAPPING FOR current_user SERVER s3;
+\deu
+   List of user mappings
+ Server |     Username      
+--------+-------------------
+ s3     | foreign_data_user
+(1 row)
+
+DROP SERVER s3;                                             -- ERROR
+ERROR:  cannot drop server s3 because other objects depend on it
+DETAIL:  user mapping for foreign_data_user depends on server s3
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP SERVER s3 CASCADE;
+NOTICE:  drop cascades to user mapping for foreign_data_user
+\des
+             List of foreign servers
+ Name |       Owner       | Foreign-data wrapper 
+------+-------------------+----------------------
+ S6   | foreign_data_user | foo
+ s4   | foreign_data_user | foo
+ s5   | foreign_data_user | foo
+ s6   | foreign_data_user | foo
+ s7   | foreign_data_user | foo
+ s8   | foreign_data_user | postgresql
+ st1  | regress_test_role | foo
+ st2  | regress_test_role | foo
+(8 rows)
+
+\deu
+List of user mappings
+ Server | Username 
+--------+----------
+(0 rows)
+
+-- CREATE USER MAPPING
+CREATE USER MAPPING FOR baz SERVER s1;                      -- ERROR
+ERROR:  role "baz" does not exist
+CREATE USER MAPPING FOR current_user SERVER s1;             -- ERROR
+ERROR:  server "s1" does not exist
+CREATE USER MAPPING FOR current_user SERVER s4;
+CREATE USER MAPPING FOR user SERVER s4;                     -- ERROR duplicate
+ERROR:  user mapping "foreign_data_user" already exists for server s4
+CREATE USER MAPPING FOR public SERVER s4 OPTIONS (mapping 'is public');
+CREATE USER MAPPING FOR user SERVER s8 OPTIONS (username 'test', password 'secret');    -- ERROR
+ERROR:  invalid option "username" to user mapping
+HINT:  valid user mapping options are: user, password
+CREATE USER MAPPING FOR user SERVER s8 OPTIONS (user 'test', password 'secret');
+ALTER SERVER s5 OWNER TO regress_test_role;
+ALTER SERVER s6 OWNER TO regress_test_indirect;
+SET ROLE regress_test_role;
+CREATE USER MAPPING FOR current_user SERVER s5;
+CREATE USER MAPPING FOR current_user SERVER s6 OPTIONS (username 'test');
+CREATE USER MAPPING FOR current_user SERVER s7;             -- ERROR
+ERROR:  must be owner of foreign server s7
+CREATE USER MAPPING FOR public SERVER s8;                   -- ERROR
+ERROR:  must be owner of foreign server s8
+RESET ROLE;
+CREATE USER MAPPING FOR current_user SERVER "S6" OPTIONS (username 'test_mixed_case');
+ALTER SERVER st1 OWNER TO regress_test_indirect;
+SET ROLE regress_test_role;
+CREATE USER MAPPING FOR current_user SERVER st1 OPTIONS (username 'bob', password 'boo');
+CREATE USER MAPPING FOR public SERVER st1;
+RESET ROLE;
+\deu
+   List of user mappings
+ Server |     Username      
+--------+-------------------
+ S6     | foreign_data_user
+ s4     | foreign_data_user
+ s4     | public
+ s5     | regress_test_role
+ s6     | regress_test_role
+ s8     | foreign_data_user
+ st1    | public
+ st1    | regress_test_role
+(8 rows)
+
+-- ALTER USER MAPPING
+ALTER USER MAPPING FOR bob SERVER s4 OPTIONS (gotcha 'true');   -- ERROR
+ERROR:  role "bob" does not exist
+ALTER USER MAPPING FOR user SERVER ss4 OPTIONS (gotcha 'true'); -- ERROR
+ERROR:  server "ss4" does not exist
+ALTER USER MAPPING FOR public SERVER s5 OPTIONS (gotcha 'true');            -- ERROR
+ERROR:  user mapping "public" does not exist for the server
+ALTER USER MAPPING FOR current_user SERVER s8 OPTIONS (username 'test');    -- ERROR
+ERROR:  invalid option "username" to user mapping
+HINT:  valid user mapping options are: user, password
+ALTER USER MAPPING FOR current_user SERVER s8 OPTIONS (DROP user, SET password 'public');
+SET ROLE regress_test_role;
+ALTER USER MAPPING FOR current_user SERVER s5 OPTIONS (ADD modified '1');
+ALTER USER MAPPING FOR public SERVER s4 OPTIONS (ADD modified '1'); -- ERROR
+ERROR:  must be owner of foreign server s4
+ALTER USER MAPPING FOR public SERVER st1 OPTIONS (ADD modified '1');
+RESET ROLE;
+\deu+
+                  List of user mappings
+ Server |     Username      |           Options           
+--------+-------------------+-----------------------------
+ S6     | foreign_data_user | {username=test_mixed_case}
+ s4     | foreign_data_user | 
+ s4     | public            | {"mapping=is public"}
+ s5     | regress_test_role | {modified=1}
+ s6     | regress_test_role | {username=test}
+ s8     | foreign_data_user | {password=public}
+ st1    | public            | {modified=1}
+ st1    | regress_test_role | {username=bob,password=boo}
+(8 rows)
+
+-- DROP USER MAPPING
+DROP USER MAPPING FOR bob SERVER s4;                        -- ERROR
+ERROR:  role "bob" does not exist
+DROP USER MAPPING FOR user SERVER ss4;
+ERROR:  server "ss4" does not exist
+DROP USER MAPPING FOR public SERVER s7;                     -- ERROR
+ERROR:  user mapping "public" does not exist for the server
+DROP USER MAPPING IF EXISTS FOR bob SERVER s4;
+NOTICE:  role "bob" does not exist, skipping
+DROP USER MAPPING IF EXISTS FOR user SERVER ss4;
+NOTICE:  server does not exist, skipping
+DROP USER MAPPING IF EXISTS FOR public SERVER s7;
+NOTICE:  user mapping "public" does not exist for the server, skipping
+CREATE USER MAPPING FOR public SERVER s8;
+SET ROLE regress_test_role;
+DROP USER MAPPING FOR public SERVER s8;                     -- ERROR
+ERROR:  must be owner of foreign server s8
+RESET ROLE;
+DROP SERVER s7;
+\deu
+   List of user mappings
+ Server |     Username      
+--------+-------------------
+ S6     | foreign_data_user
+ s4     | foreign_data_user
+ s4     | public
+ s5     | regress_test_role
+ s6     | regress_test_role
+ s8     | foreign_data_user
+ s8     | public
+ st1    | public
+ st1    | regress_test_role
+(9 rows)
+
+-- Information schema
+SELECT * FROM information_schema.foreign_data_wrappers ORDER BY 1, 2;
+ foreign_data_wrapper_catalog | foreign_data_wrapper_name | authorization_identifier |  library_name  | foreign_data_wrapper_language 
+------------------------------+---------------------------+--------------------------+----------------+-------------------------------
+ regression                   | dummy                     | foreign_data_user        | dummy_fdw      | c
+ regression                   | foo                       | foreign_data_user        | dummy_fdw      | c
+ regression                   | postgresql                | foreign_data_user        | postgresql_fdw | c
+(3 rows)
+
+SELECT * FROM information_schema.foreign_data_wrapper_options ORDER BY 1, 2, 3;
+ foreign_data_wrapper_catalog | foreign_data_wrapper_name | option_name  | option_value 
+------------------------------+---------------------------+--------------+--------------
+ regression                   | foo                       | test_wrapper | true
+(1 row)
+
+SELECT * FROM information_schema.foreign_servers ORDER BY 1, 2;
+ foreign_server_catalog | foreign_server_name | foreign_data_wrapper_catalog | foreign_data_wrapper_name | foreign_server_type | foreign_server_version | authorization_identifier 
+------------------------+---------------------+------------------------------+---------------------------+---------------------+------------------------+--------------------------
+ regression             | S6                  | regression                   | foo                       |                     |                        | foreign_data_user
+ regression             | s4                  | regression                   | foo                       | oracle              |                        | foreign_data_user
+ regression             | s5                  | regression                   | foo                       |                     | 15.0                   | regress_test_role
+ regression             | s6                  | regression                   | foo                       |                     | 16.0                   | regress_test_indirect
+ regression             | s8                  | regression                   | postgresql                |                     |                        | foreign_data_user
+ regression             | st1                 | regression                   | foo                       |                     |                        | regress_test_indirect
+ regression             | st2                 | regression                   | foo                       |                     |                        | regress_test_role
+(7 rows)
+
+SELECT * FROM information_schema.foreign_server_options ORDER BY 1, 2, 3;
+ foreign_server_catalog | foreign_server_name |   option_name    | option_value 
+------------------------+---------------------+------------------+--------------
+ regression             | S6                  | mixed_case_names | true
+ regression             | s4                  | dbname           | b
+ regression             | s4                  | host             | a
+ regression             | s6                  | dbname           | b
+ regression             | s6                  | host             | a
+ regression             | s8                  | connect_timeout  | 30
+ regression             | s8                  | dbname           | db1
+(7 rows)
+
+SELECT * FROM information_schema.user_mappings ORDER BY 1, 2, 3;
+ authorization_identifier | foreign_server_catalog | foreign_server_name 
+--------------------------+------------------------+---------------------
+ PUBLIC                   | regression             | s4
+ PUBLIC                   | regression             | s8
+ PUBLIC                   | regression             | st1
+ foreign_data_user        | regression             | S6
+ foreign_data_user        | regression             | s4
+ foreign_data_user        | regression             | s8
+ regress_test_role        | regression             | s5
+ regress_test_role        | regression             | s6
+ regress_test_role        | regression             | st1
+(9 rows)
+
+SELECT * FROM information_schema.user_mapping_options ORDER BY 1, 2, 3, 4;
+ authorization_identifier | foreign_server_catalog | foreign_server_name | option_name |  option_value   
+--------------------------+------------------------+---------------------+-------------+-----------------
+ PUBLIC                   | regression             | s4                  | mapping     | is public
+ PUBLIC                   | regression             | st1                 | modified    | 1
+ foreign_data_user        | regression             | S6                  | username    | test_mixed_case
+ foreign_data_user        | regression             | s8                  | password    | public
+ regress_test_role        | regression             | s5                  | modified    | 1
+ regress_test_role        | regression             | s6                  | username    | test
+ regress_test_role        | regression             | st1                 | password    | boo
+ regress_test_role        | regression             | st1                 | username    | bob
+(8 rows)
+
+SELECT * FROM information_schema.usage_privileges WHERE object_type LIKE 'FOREIGN%' ORDER BY 1, 2, 3, 4, 5;
+      grantor      |        grantee        | object_catalog | object_schema | object_name |     object_type      | privilege_type | is_grantable 
+-------------------+-----------------------+----------------+---------------+-------------+----------------------+----------------+--------------
+ foreign_data_user | foreign_data_user     | regression     |               | foo         | FOREIGN DATA WRAPPER | USAGE          | NO
+ foreign_data_user | foreign_data_user     | regression     |               | s6          | FOREIGN SERVER       | USAGE          | NO
+ foreign_data_user | regress_test_indirect | regression     |               | foo         | FOREIGN DATA WRAPPER | USAGE          | NO
+ foreign_data_user | regress_test_role2    | regression     |               | s6          | FOREIGN SERVER       | USAGE          | YES
+(4 rows)
+
+SELECT * FROM information_schema.role_usage_grants WHERE object_type LIKE 'FOREIGN%' ORDER BY 1, 2, 3, 4, 5;
+      grantor      |        grantee        | object_catalog | object_schema | object_name |     object_type      | privilege_type | is_grantable 
+-------------------+-----------------------+----------------+---------------+-------------+----------------------+----------------+--------------
+ foreign_data_user | foreign_data_user     | regression     |               | foo         | FOREIGN DATA WRAPPER | USAGE          | NO
+ foreign_data_user | foreign_data_user     | regression     |               | s6          | FOREIGN SERVER       | USAGE          | NO
+ foreign_data_user | regress_test_indirect | regression     |               | foo         | FOREIGN DATA WRAPPER | USAGE          | NO
+ foreign_data_user | regress_test_role2    | regression     |               | s6          | FOREIGN SERVER       | USAGE          | YES
+(4 rows)
+
+SET ROLE regress_test_role;
+SELECT * FROM information_schema.user_mapping_options ORDER BY 1, 2, 3, 4;
+ authorization_identifier | foreign_server_catalog | foreign_server_name | option_name | option_value 
+--------------------------+------------------------+---------------------+-------------+--------------
+ PUBLIC                   | regression             | st1                 | modified    | 1
+ regress_test_role        | regression             | s5                  | modified    | 1
+ regress_test_role        | regression             | s6                  | username    | test
+ regress_test_role        | regression             | st1                 | password    | boo
+ regress_test_role        | regression             | st1                 | username    | bob
+(5 rows)
+
+SELECT * FROM information_schema.usage_privileges WHERE object_type LIKE 'FOREIGN%' ORDER BY 1, 2, 3, 4, 5;
+      grantor      |        grantee        | object_catalog | object_schema | object_name |     object_type      | privilege_type | is_grantable 
+-------------------+-----------------------+----------------+---------------+-------------+----------------------+----------------+--------------
+ foreign_data_user | regress_test_indirect | regression     |               | foo         | FOREIGN DATA WRAPPER | USAGE          | NO
+ foreign_data_user | regress_test_role2    | regression     |               | s6          | FOREIGN SERVER       | USAGE          | YES
+(2 rows)
+
+SELECT * FROM information_schema.role_usage_grants WHERE object_type LIKE 'FOREIGN%' ORDER BY 1, 2, 3, 4, 5;
+      grantor      |        grantee        | object_catalog | object_schema | object_name |     object_type      | privilege_type | is_grantable 
+-------------------+-----------------------+----------------+---------------+-------------+----------------------+----------------+--------------
+ foreign_data_user | regress_test_indirect | regression     |               | foo         | FOREIGN DATA WRAPPER | USAGE          | NO
+ foreign_data_user | regress_test_role2    | regression     |               | s6          | FOREIGN SERVER       | USAGE          | YES
+(2 rows)
+
+DROP USER MAPPING FOR current_user SERVER st1;
+RESET ROLE;
+-- has_foreign_data_wrapper_privilege
+SELECT has_foreign_data_wrapper_privilege('regress_test_role',
+    (SELECT oid FROM pg_foreign_data_wrapper WHERE fdwname='foo'), 'USAGE');
+ has_foreign_data_wrapper_privilege 
+------------------------------------
+ t
+(1 row)
+
+SELECT has_foreign_data_wrapper_privilege('regress_test_role', 'foo', 'USAGE');
+ has_foreign_data_wrapper_privilege 
+------------------------------------
+ t
+(1 row)
+
+SELECT has_foreign_data_wrapper_privilege(
+    (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'),
+    (SELECT oid FROM pg_foreign_data_wrapper WHERE fdwname='foo'), 'USAGE');
+ has_foreign_data_wrapper_privilege 
+------------------------------------
+ t
+(1 row)
+
+SELECT has_foreign_data_wrapper_privilege(
+    (SELECT oid FROM pg_foreign_data_wrapper WHERE fdwname='foo'), 'USAGE');
+ has_foreign_data_wrapper_privilege 
+------------------------------------
+ t
+(1 row)
+
+SELECT has_foreign_data_wrapper_privilege(
+    (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'), 'foo', 'USAGE');
+ has_foreign_data_wrapper_privilege 
+------------------------------------
+ t
+(1 row)
+
+SELECT has_foreign_data_wrapper_privilege('foo', 'USAGE');
+ has_foreign_data_wrapper_privilege 
+------------------------------------
+ t
+(1 row)
+
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role;
+SELECT has_foreign_data_wrapper_privilege('regress_test_role', 'foo', 'USAGE');
+ has_foreign_data_wrapper_privilege 
+------------------------------------
+ t
+(1 row)
+
+-- has_server_privilege
+SELECT has_server_privilege('regress_test_role',
+    (SELECT oid FROM pg_foreign_server WHERE srvname='s8'), 'USAGE');
+ has_server_privilege 
+----------------------
+ f
+(1 row)
+
+SELECT has_server_privilege('regress_test_role', 's8', 'USAGE');
+ has_server_privilege 
+----------------------
+ f
+(1 row)
+
+SELECT has_server_privilege(
+    (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'),
+    (SELECT oid FROM pg_foreign_server WHERE srvname='s8'), 'USAGE');
+ has_server_privilege 
+----------------------
+ f
+(1 row)
+
+SELECT has_server_privilege(
+    (SELECT oid FROM pg_foreign_server WHERE srvname='s8'), 'USAGE');
+ has_server_privilege 
+----------------------
+ t
+(1 row)
+
+SELECT has_server_privilege(
+    (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'), 's8', 'USAGE');
+ has_server_privilege 
+----------------------
+ f
+(1 row)
+
+SELECT has_server_privilege('s8', 'USAGE');
+ has_server_privilege 
+----------------------
+ t
+(1 row)
+
+GRANT USAGE ON FOREIGN SERVER s8 TO regress_test_role;
+SELECT has_server_privilege('regress_test_role', 's8', 'USAGE');
+ has_server_privilege 
+----------------------
+ t
+(1 row)
+
+REVOKE USAGE ON FOREIGN SERVER s8 FROM regress_test_role;
+GRANT USAGE ON FOREIGN SERVER s4 TO regress_test_role;
+DROP USER MAPPING FOR public SERVER s4;
+ALTER SERVER s6 OPTIONS (DROP host, DROP dbname);
+ALTER USER MAPPING FOR regress_test_role SERVER s6 OPTIONS (DROP username);
+ALTER FOREIGN DATA WRAPPER foo LIBRARY 'plpgsql';
+WARNING:  changing the foreign-data wrapper library can cause the options for dependent objects to become invalid
+ALTER FOREIGN DATA WRAPPER foo LIBRARY 'default_fdw';
+ERROR:  could not access file "default_fdw": No such file or directory
+-- Privileges
+SET ROLE unprivileged_role;
+CREATE FOREIGN DATA WRAPPER foobar LIBRARY 'dummy_fdw' LANGUAGE C; -- ERROR
+ERROR:  permission denied to create foreign-data wrapper "foobar"
+HINT:  Must be superuser to create a foreign-data wrapper.
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (gotcha 'true');         -- ERROR
+ERROR:  permission denied to alter foreign-data wrapper "foo"
+HINT:  Must be superuser to alter a foreign-data wrapper.
+ALTER FOREIGN DATA WRAPPER foo OWNER TO unprivileged_role;      -- ERROR
+ERROR:  permission denied to change owner of foreign-data wrapper "foo"
+HINT:  Must be superuser to change owner of a foreign-data wrapper.
+DROP FOREIGN DATA WRAPPER foo;                                  -- ERROR
+ERROR:  permission denied to drop foreign-data wrapper "foo"
+HINT:  Must be superuser to drop a foreign-data wrapper.
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role;   -- ERROR
+ERROR:  permission denied for foreign-data wrapper foo
+CREATE SERVER s9 FOREIGN DATA WRAPPER foo;                      -- ERROR
+ERROR:  permission denied for foreign-data wrapper foo
+ALTER SERVER s4 VERSION '0.5';                                  -- ERROR
+ERROR:  must be owner of foreign server s4
+ALTER SERVER s4 OWNER TO unprivileged_role;                     -- ERROR
+ERROR:  must be owner of foreign server s4
+DROP SERVER s4;                                                 -- ERROR
+ERROR:  must be owner of foreign server s4
+GRANT USAGE ON FOREIGN SERVER s4 TO regress_test_role;          -- ERROR
+ERROR:  permission denied for foreign server s4
+CREATE USER MAPPING FOR public SERVER s4;                       -- ERROR
+ERROR:  must be owner of foreign server s4
+ALTER USER MAPPING FOR regress_test_role SERVER s6 OPTIONS (gotcha 'true'); -- ERROR
+ERROR:  must be owner of foreign server s6
+DROP USER MAPPING FOR regress_test_role SERVER s6;              -- ERROR
+ERROR:  must be owner of foreign server s6
+RESET ROLE;
+GRANT USAGE ON FOREIGN DATA WRAPPER postgresql TO unprivileged_role;
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO unprivileged_role WITH GRANT OPTION;
+SET ROLE unprivileged_role;
+CREATE FOREIGN DATA WRAPPER foobar LIBRARY 'dummy_fdw' LANGUAGE C; -- ERROR
+ERROR:  permission denied to create foreign-data wrapper "foobar"
+HINT:  Must be superuser to create a foreign-data wrapper.
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (gotcha 'true');         -- ERROR
+ERROR:  permission denied to alter foreign-data wrapper "foo"
+HINT:  Must be superuser to alter a foreign-data wrapper.
+DROP FOREIGN DATA WRAPPER foo;                                  -- ERROR
+ERROR:  permission denied to drop foreign-data wrapper "foo"
+HINT:  Must be superuser to drop a foreign-data wrapper.
+GRANT USAGE ON FOREIGN DATA WRAPPER postgresql TO regress_test_role; -- WARNING
+WARNING:  no privileges were granted for "postgresql"
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role;
+CREATE SERVER s9 FOREIGN DATA WRAPPER postgresql;
+ALTER SERVER s6 VERSION '0.5';                                  -- ERROR
+ERROR:  must be owner of foreign server s6
+DROP SERVER s6;                                                 -- ERROR
+ERROR:  must be owner of foreign server s6
+GRANT USAGE ON FOREIGN SERVER s6 TO regress_test_role;          -- ERROR
+ERROR:  permission denied for foreign server s6
+GRANT USAGE ON FOREIGN SERVER s9 TO regress_test_role;
+CREATE USER MAPPING FOR public SERVER s6;                       -- ERROR
+ERROR:  must be owner of foreign server s6
+CREATE USER MAPPING FOR public SERVER s9;
+ALTER USER MAPPING FOR regress_test_role SERVER s6 OPTIONS (gotcha 'true'); -- ERROR
+ERROR:  must be owner of foreign server s6
+DROP USER MAPPING FOR regress_test_role SERVER s6;              -- ERROR
+ERROR:  must be owner of foreign server s6
+RESET ROLE;
+REVOKE USAGE ON FOREIGN DATA WRAPPER foo FROM unprivileged_role; -- ERROR
+ERROR:  dependent privileges exist
+HINT:  Use CASCADE to revoke them too.
+REVOKE USAGE ON FOREIGN DATA WRAPPER foo FROM unprivileged_role CASCADE;
+SET ROLE unprivileged_role;
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role;   -- ERROR
+ERROR:  permission denied for foreign-data wrapper foo
+CREATE SERVER s10 FOREIGN DATA WRAPPER foo;                     -- ERROR
+ERROR:  permission denied for foreign-data wrapper foo
+ALTER SERVER s9 VERSION '1.1';
+GRANT USAGE ON FOREIGN SERVER s9 TO regress_test_role;
+CREATE USER MAPPING FOR current_user SERVER s9;
+DROP SERVER s9 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to user mapping for public
+drop cascades to user mapping for unprivileged_role
+RESET ROLE;
+CREATE SERVER s9 FOREIGN DATA WRAPPER foo;
+GRANT USAGE ON FOREIGN SERVER s9 TO unprivileged_role;
+SET ROLE unprivileged_role;
+ALTER SERVER s9 VERSION '1.2';                                  -- ERROR
+ERROR:  must be owner of foreign server s9
+GRANT USAGE ON FOREIGN SERVER s9 TO regress_test_role;          -- WARNING
+WARNING:  no privileges were granted for "s9"
+CREATE USER MAPPING FOR current_user SERVER s9;                 -- ERROR
+ERROR:  must be owner of foreign server s9
+DROP SERVER s9 CASCADE;                                         -- ERROR
+ERROR:  must be owner of foreign server s9
+RESET ROLE;
+-- Cleanup
+DROP ROLE regress_test_role;                                -- ERROR
+ERROR:  role "regress_test_role" cannot be dropped because some objects depend on it
+DETAIL:  access to server s4
+access to foreign-data wrapper foo
+owner of user mapping for regress_test_role
+owner of user mapping for regress_test_role
+owner of server s5
+owner of server st2
+DROP SERVER s5 CASCADE;
+NOTICE:  drop cascades to user mapping for regress_test_role
+DROP SERVER st1 CASCADE;
+NOTICE:  drop cascades to user mapping for public
+DROP SERVER st2;
+DROP USER MAPPING FOR regress_test_role SERVER s6;
+DROP FOREIGN DATA WRAPPER foo CASCADE;
+NOTICE:  drop cascades to 6 other objects
+DETAIL:  drop cascades to server s4
+drop cascades to user mapping for foreign_data_user
+drop cascades to server s6
+drop cascades to server S6
+drop cascades to user mapping for foreign_data_user
+drop cascades to server s9
+DROP SERVER s8 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to user mapping for foreign_data_user
+drop cascades to user mapping for public
+DROP ROLE regress_test_indirect;
+DROP ROLE regress_test_role;
+DROP ROLE unprivileged_role;                                -- ERROR
+ERROR:  role "unprivileged_role" cannot be dropped because some objects depend on it
+DETAIL:  access to foreign-data wrapper postgresql
+REVOKE ALL ON FOREIGN DATA WRAPPER postgresql FROM unprivileged_role;
+DROP ROLE unprivileged_role;
+DROP ROLE regress_test_role2;
+DROP FOREIGN DATA WRAPPER postgresql CASCADE;
+DROP FOREIGN DATA WRAPPER dummy CASCADE;
+\c
+DROP ROLE foreign_data_user;
+-- At this point we should have no wrappers, no servers, and no mappings.
+SELECT fdwname, fdwlibrary, fdwoptions FROM pg_foreign_data_wrapper;
+ fdwname | fdwlibrary | fdwoptions 
+---------+------------+------------
+(0 rows)
+
+SELECT srvname, srvoptions FROM pg_foreign_server;
+ srvname | srvoptions 
+---------+------------
+(0 rows)
+
+SELECT * FROM pg_user_mapping;
+ umuser | umserver | umoptions 
+--------+----------+-----------
+(0 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 80b002d5388440df5d0b0ad5edf000b474c9519f..977b17cd51b4014060df6b7d3758d2110120d205 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1313,6 +1313,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
  pg_timezone_abbrevs      | SELECT pg_timezone_abbrevs.abbrev, pg_timezone_abbrevs.utc_offset, pg_timezone_abbrevs.is_dst FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
  pg_timezone_names        | SELECT pg_timezone_names.name, pg_timezone_names.abbrev, pg_timezone_names.utc_offset, pg_timezone_names.is_dst FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
  pg_user                  | SELECT pg_shadow.usename, pg_shadow.usesysid, pg_shadow.usecreatedb, pg_shadow.usesuper, pg_shadow.usecatupd, '********'::text AS passwd, pg_shadow.valuntil, pg_shadow.useconfig FROM pg_shadow;
+ pg_user_mappings         | SELECT u.oid AS umid, s.oid AS srvid, s.srvname, u.umuser, CASE WHEN (u.umuser = (0)::oid) THEN 'public'::name ELSE a.rolname END AS usename, CASE WHEN (pg_has_role(s.srvowner, 'USAGE'::text) OR has_server_privilege(s.oid, 'USAGE'::text)) THEN u.umoptions ELSE NULL::text[] END AS umoptions FROM ((pg_user_mapping u LEFT JOIN pg_authid a ON ((a.oid = u.umuser))) JOIN pg_foreign_server s ON ((u.umserver = s.oid)));
  pg_views                 | SELECT n.nspname AS schemaname, c.relname AS viewname, pg_get_userbyid(c.relowner) AS viewowner, pg_get_viewdef(c.oid) AS definition FROM (pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = 'v'::"char");
  rtest_v1                 | SELECT rtest_t1.a, rtest_t1.b FROM rtest_t1;
  rtest_vcomp              | SELECT x.part, (x.size * y.factor) AS size_in_cm FROM rtest_comp x, rtest_unitfact y WHERE (x.unit = y.unit);
@@ -1328,7 +1329,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
  shoelace_obsolete        | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE (shoe.slcolor = shoelace.sl_color))));
  street                   | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ## r.thepath);
  toyemp                   | SELECT emp.name, emp.age, emp.location, (12 * emp.salary) AS annualsal FROM emp;
-(50 rows)
+(51 rows)
 
 SELECT tablename, rulename, definition FROM pg_rules 
 	ORDER BY tablename, rulename;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6946b2ec163c75b1e5396b444eb8e15a7c6583fd..e677d4580fdcafa85a05165db435bcc3e7a5e92a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -99,6 +99,8 @@ SELECT relname, relhasindex
  pg_depend               | t
  pg_description          | t
  pg_enum                 | t
+ pg_foreign_data_wrapper | t
+ pg_foreign_server       | t
  pg_index                | t
  pg_inherits             | t
  pg_language             | t
@@ -122,6 +124,7 @@ SELECT relname, relhasindex
  pg_ts_parser            | t
  pg_ts_template          | t
  pg_type                 | t
+ pg_user_mapping         | t
  point_tbl               | f
  polygon_tbl             | t
  ramp                    | f
@@ -149,7 +152,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(138 rows)
+(141 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 5257b463018541c40779bee253a34f04bd87b87d..27b79aef256a5afd457222364523c2dd7e3081b0 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -1,5 +1,5 @@
 # ----------
-# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.50 2008/10/31 09:17:16 heikki Exp $
+# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.51 2008/12/19 16:25:19 petere Exp $
 #
 # By convention, we put no more than twenty tests in any one parallel group;
 # this limits the number of connections needed to run the tests.
@@ -77,7 +77,7 @@ test: misc
 # ----------
 # Another group of parallel tests
 # ----------
-test: select_views portals_p2 rules foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts
+test: select_views portals_p2 rules foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 70dd424849ca5d63d50f031b8d507c3defa57456..da823d8423810f7a433bbaa7ef9c6a165061f3d3 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -1,4 +1,4 @@
-# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.47 2008/10/31 09:17:16 heikki Exp $
+# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.48 2008/12/19 16:25:19 petere Exp $
 # This should probably be in an order similar to parallel_schedule.
 test: boolean
 test: char
@@ -108,6 +108,7 @@ test: prepare
 test: without_oid
 test: conversion
 test: tsdicts
+test: foreign_data
 test: truncate
 test: alter_table
 test: sequence
diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql
new file mode 100644
index 0000000000000000000000000000000000000000..03de174a6bc6cb806ab401696218a5b50b9536c0
--- /dev/null
+++ b/src/test/regress/sql/foreign_data.sql
@@ -0,0 +1,396 @@
+--
+-- Test foreign-data wrapper and server management.
+--
+
+-- Clean up in case a prior regression run failed
+
+-- Suppress NOTICE messages when roles don't exist
+SET client_min_messages TO 'error';
+
+DROP ROLE IF EXISTS foreign_data_user, regress_test_role, regress_test_role2, regress_test_role_super, regress_test_indirect, unpriviled_role;
+
+RESET client_min_messages;
+
+CREATE ROLE foreign_data_user LOGIN SUPERUSER;
+SET SESSION AUTHORIZATION 'foreign_data_user';
+
+CREATE ROLE regress_test_role;
+CREATE ROLE regress_test_role2;
+CREATE ROLE regress_test_role_super SUPERUSER;
+CREATE ROLE regress_test_indirect;
+CREATE ROLE unprivileged_role;
+
+CREATE FOREIGN DATA WRAPPER dummy LIBRARY 'dummy_fdw' LANGUAGE C;
+CREATE FOREIGN DATA WRAPPER postgresql LIBRARY 'postgresql_fdw' LANGUAGE C;
+
+-- At this point we should have 2 built-in wrappers and no servers.
+SELECT fdwname, fdwlibrary, fdwoptions FROM pg_foreign_data_wrapper ORDER BY 1, 2, 3;
+SELECT srvname, srvoptions FROM pg_foreign_server;
+SELECT * FROM pg_user_mapping;
+
+-- CREATE FOREIGN DATA WRAPPER
+CREATE FOREIGN DATA WRAPPER foo LIBRARY '' LANGUAGE C;            -- ERROR
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'plpgsql' LANGUAGE C;
+DROP FOREIGN DATA WRAPPER foo;
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C;
+\dew
+
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C; -- duplicate
+CREATE FOREIGN DATA WRAPPER "Foo" LIBRARY 'dummy_fdw' LANGUAGE C;
+DROP FOREIGN DATA WRAPPER "Foo";
+DROP FOREIGN DATA WRAPPER foo;
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C OPTIONS (testing '1');
+\dew+
+
+DROP FOREIGN DATA WRAPPER foo;
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C OPTIONS (testing '1', testing '2');   -- ERROR
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C OPTIONS (testing '1', another '2');
+\dew+
+
+DROP FOREIGN DATA WRAPPER foo;
+SET ROLE regress_test_role;
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C; -- ERROR
+RESET ROLE;
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'postgresql_fdw' LANGUAGE C;
+\dew+
+
+-- ALTER FOREIGN DATA WRAPPER
+ALTER FOREIGN DATA WRAPPER foo LIBRARY '';                  -- ERROR
+ALTER FOREIGN DATA WRAPPER foo LIBRARY 'plpgsql';
+ALTER FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw';
+\dew+
+
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '1', b '2');
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (SET c '4');         -- ERROR
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (DROP c);            -- ERROR
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD x '1', DROP x);
+\dew+
+
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (DROP a, SET b '3', ADD c '4');
+\dew+
+
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (a '2');
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (b '4');             -- ERROR
+\dew+
+
+SET ROLE regress_test_role;
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD d '5');         -- ERROR
+SET ROLE regress_test_role_super;
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD d '5');
+\dew+
+
+ALTER FOREIGN DATA WRAPPER foo OWNER TO regress_test_role;  -- ERROR
+ALTER FOREIGN DATA WRAPPER foo OWNER TO regress_test_role_super;
+ALTER ROLE regress_test_role_super NOSUPERUSER;
+SET ROLE regress_test_role_super;
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (ADD e '6');         -- ERROR
+RESET ROLE;
+\dew+
+
+-- DROP FOREIGN DATA WRAPPER
+DROP FOREIGN DATA WRAPPER nonexistent;                      -- ERROR
+DROP FOREIGN DATA WRAPPER IF EXISTS nonexistent;
+\dew+
+
+DROP ROLE regress_test_role_super;                          -- ERROR
+SET ROLE regress_test_role_super;
+DROP FOREIGN DATA WRAPPER foo;                              -- ERROR
+RESET ROLE;
+ALTER ROLE regress_test_role_super SUPERUSER;
+DROP FOREIGN DATA WRAPPER foo;
+DROP ROLE regress_test_role_super;
+\dew+
+
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C;
+CREATE SERVER s1 FOREIGN DATA WRAPPER foo;
+CREATE USER MAPPING FOR current_user SERVER s1;
+\dew+
+\des+
+\deu+
+DROP FOREIGN DATA WRAPPER foo;                              -- ERROR
+SET ROLE regress_test_role;
+DROP FOREIGN DATA WRAPPER foo CASCADE;                      -- ERROR
+RESET ROLE;
+DROP FOREIGN DATA WRAPPER foo CASCADE;
+\dew+
+\des+
+\deu+
+
+-- exercise CREATE SERVER
+CREATE SERVER s1 FOREIGN DATA WRAPPER foo;                  -- ERROR
+CREATE FOREIGN DATA WRAPPER foo LIBRARY 'dummy_fdw' LANGUAGE C OPTIONS (test_wrapper 'true');
+CREATE SERVER s1 FOREIGN DATA WRAPPER foo;
+CREATE SERVER s1 FOREIGN DATA WRAPPER foo;                  -- ERROR
+CREATE SERVER s2 FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbname 'b');
+CREATE SERVER s3 TYPE 'oracle' FOREIGN DATA WRAPPER foo;
+CREATE SERVER s4 TYPE 'oracle' FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbname 'b');
+CREATE SERVER s5 VERSION '15.0' FOREIGN DATA WRAPPER foo;
+CREATE SERVER s6 VERSION '16.0' FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbname 'b');
+CREATE SERVER "S6" FOREIGN DATA WRAPPER foo OPTIONS (mixed_case_names 'true');
+CREATE SERVER s7 TYPE 'oracle' VERSION '17.0' FOREIGN DATA WRAPPER foo OPTIONS (host 'a', dbname 'b');
+CREATE SERVER s8 FOREIGN DATA WRAPPER postgresql OPTIONS (foo '1'); -- ERROR
+CREATE SERVER s8 FOREIGN DATA WRAPPER postgresql OPTIONS (host 'localhost', dbname 's8db');
+\des+
+SET ROLE regress_test_role;
+CREATE SERVER st1 FOREIGN DATA WRAPPER foo;                 -- ERROR: no usage on FDW
+RESET ROLE;
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role;
+SET ROLE regress_test_role;
+CREATE SERVER st1 FOREIGN DATA WRAPPER foo;
+RESET ROLE;
+\des+
+
+REVOKE USAGE ON FOREIGN DATA WRAPPER foo FROM regress_test_role;
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_indirect;
+SET ROLE regress_test_role;
+CREATE SERVER st2 FOREIGN DATA WRAPPER foo;                 -- ERROR
+RESET ROLE;
+GRANT regress_test_indirect TO regress_test_role;
+SET ROLE regress_test_role;
+CREATE SERVER st2 FOREIGN DATA WRAPPER foo;
+\des+
+RESET ROLE;
+REVOKE regress_test_indirect FROM regress_test_role;
+
+-- ALTER SERVER
+ALTER SERVER s0;                                            -- ERROR
+ALTER SERVER s0 OPTIONS (a '1');                            -- ERROR
+ALTER SERVER s1 VERSION '1.0' OPTIONS (servername 's1');
+ALTER SERVER s2 VERSION '1.1';
+ALTER SERVER s3 OPTIONS (tnsname 'orcl', port '1521');
+GRANT USAGE ON FOREIGN SERVER s1 TO regress_test_role;
+GRANT USAGE ON FOREIGN SERVER s6 TO regress_test_role2 WITH GRANT OPTION;
+\des+
+SET ROLE regress_test_role;
+ALTER SERVER s1 VERSION '1.1';                              -- ERROR
+ALTER SERVER s1 OWNER TO regress_test_role;                 -- ERROR
+RESET ROLE;
+ALTER SERVER s1 OWNER TO regress_test_role;
+GRANT regress_test_role2 TO regress_test_role;
+SET ROLE regress_test_role;
+ALTER SERVER s1 VERSION '1.1';
+ALTER SERVER s1 OWNER TO regress_test_role2;                -- ERROR
+RESET ROLE;
+ALTER SERVER s8 OPTIONS (foo '1');                          -- ERROR option validation
+ALTER SERVER s8 OPTIONS (connect_timeout '30', SET dbname 'db1', DROP host);
+SET ROLE regress_test_role;
+ALTER SERVER s1 OWNER TO regress_test_indirect;             -- ERROR
+RESET ROLE;
+GRANT regress_test_indirect TO regress_test_role;
+SET ROLE regress_test_role;
+ALTER SERVER s1 OWNER TO regress_test_indirect;
+RESET ROLE;
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_indirect;
+SET ROLE regress_test_role;
+ALTER SERVER s1 OWNER TO regress_test_indirect;
+RESET ROLE;
+DROP ROLE regress_test_indirect;                            -- ERROR
+\des+
+
+-- DROP SERVER
+DROP SERVER nonexistent;                                    -- ERROR
+DROP SERVER IF EXISTS nonexistent;
+\des
+SET ROLE regress_test_role;
+DROP SERVER s2;                                             -- ERROR
+DROP SERVER s1;
+RESET ROLE;
+\des
+ALTER SERVER s2 OWNER TO regress_test_role;
+SET ROLE regress_test_role;
+DROP SERVER s2;
+RESET ROLE;
+\des
+CREATE USER MAPPING FOR current_user SERVER s3;
+\deu
+DROP SERVER s3;                                             -- ERROR
+DROP SERVER s3 CASCADE;
+\des
+\deu
+
+-- CREATE USER MAPPING
+CREATE USER MAPPING FOR baz SERVER s1;                      -- ERROR
+CREATE USER MAPPING FOR current_user SERVER s1;             -- ERROR
+CREATE USER MAPPING FOR current_user SERVER s4;
+CREATE USER MAPPING FOR user SERVER s4;                     -- ERROR duplicate
+CREATE USER MAPPING FOR public SERVER s4 OPTIONS (mapping 'is public');
+CREATE USER MAPPING FOR user SERVER s8 OPTIONS (username 'test', password 'secret');    -- ERROR
+CREATE USER MAPPING FOR user SERVER s8 OPTIONS (user 'test', password 'secret');
+ALTER SERVER s5 OWNER TO regress_test_role;
+ALTER SERVER s6 OWNER TO regress_test_indirect;
+SET ROLE regress_test_role;
+CREATE USER MAPPING FOR current_user SERVER s5;
+CREATE USER MAPPING FOR current_user SERVER s6 OPTIONS (username 'test');
+CREATE USER MAPPING FOR current_user SERVER s7;             -- ERROR
+CREATE USER MAPPING FOR public SERVER s8;                   -- ERROR
+RESET ROLE;
+CREATE USER MAPPING FOR current_user SERVER "S6" OPTIONS (username 'test_mixed_case');
+
+ALTER SERVER st1 OWNER TO regress_test_indirect;
+SET ROLE regress_test_role;
+CREATE USER MAPPING FOR current_user SERVER st1 OPTIONS (username 'bob', password 'boo');
+CREATE USER MAPPING FOR public SERVER st1;
+RESET ROLE;
+\deu
+
+-- ALTER USER MAPPING
+ALTER USER MAPPING FOR bob SERVER s4 OPTIONS (gotcha 'true');   -- ERROR
+ALTER USER MAPPING FOR user SERVER ss4 OPTIONS (gotcha 'true'); -- ERROR
+ALTER USER MAPPING FOR public SERVER s5 OPTIONS (gotcha 'true');            -- ERROR
+ALTER USER MAPPING FOR current_user SERVER s8 OPTIONS (username 'test');    -- ERROR
+ALTER USER MAPPING FOR current_user SERVER s8 OPTIONS (DROP user, SET password 'public');
+SET ROLE regress_test_role;
+ALTER USER MAPPING FOR current_user SERVER s5 OPTIONS (ADD modified '1');
+ALTER USER MAPPING FOR public SERVER s4 OPTIONS (ADD modified '1'); -- ERROR
+ALTER USER MAPPING FOR public SERVER st1 OPTIONS (ADD modified '1');
+RESET ROLE;
+\deu+
+
+-- DROP USER MAPPING
+DROP USER MAPPING FOR bob SERVER s4;                        -- ERROR
+DROP USER MAPPING FOR user SERVER ss4;
+DROP USER MAPPING FOR public SERVER s7;                     -- ERROR
+DROP USER MAPPING IF EXISTS FOR bob SERVER s4;
+DROP USER MAPPING IF EXISTS FOR user SERVER ss4;
+DROP USER MAPPING IF EXISTS FOR public SERVER s7;
+CREATE USER MAPPING FOR public SERVER s8;
+SET ROLE regress_test_role;
+DROP USER MAPPING FOR public SERVER s8;                     -- ERROR
+RESET ROLE;
+DROP SERVER s7;
+\deu
+
+-- Information schema
+
+SELECT * FROM information_schema.foreign_data_wrappers ORDER BY 1, 2;
+SELECT * FROM information_schema.foreign_data_wrapper_options ORDER BY 1, 2, 3;
+SELECT * FROM information_schema.foreign_servers ORDER BY 1, 2;
+SELECT * FROM information_schema.foreign_server_options ORDER BY 1, 2, 3;
+SELECT * FROM information_schema.user_mappings ORDER BY 1, 2, 3;
+SELECT * FROM information_schema.user_mapping_options ORDER BY 1, 2, 3, 4;
+SELECT * FROM information_schema.usage_privileges WHERE object_type LIKE 'FOREIGN%' ORDER BY 1, 2, 3, 4, 5;
+SELECT * FROM information_schema.role_usage_grants WHERE object_type LIKE 'FOREIGN%' ORDER BY 1, 2, 3, 4, 5;
+SET ROLE regress_test_role;
+SELECT * FROM information_schema.user_mapping_options ORDER BY 1, 2, 3, 4;
+SELECT * FROM information_schema.usage_privileges WHERE object_type LIKE 'FOREIGN%' ORDER BY 1, 2, 3, 4, 5;
+SELECT * FROM information_schema.role_usage_grants WHERE object_type LIKE 'FOREIGN%' ORDER BY 1, 2, 3, 4, 5;
+DROP USER MAPPING FOR current_user SERVER st1;
+RESET ROLE;
+
+
+-- has_foreign_data_wrapper_privilege
+SELECT has_foreign_data_wrapper_privilege('regress_test_role',
+    (SELECT oid FROM pg_foreign_data_wrapper WHERE fdwname='foo'), 'USAGE');
+SELECT has_foreign_data_wrapper_privilege('regress_test_role', 'foo', 'USAGE');
+SELECT has_foreign_data_wrapper_privilege(
+    (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'),
+    (SELECT oid FROM pg_foreign_data_wrapper WHERE fdwname='foo'), 'USAGE');
+SELECT has_foreign_data_wrapper_privilege(
+    (SELECT oid FROM pg_foreign_data_wrapper WHERE fdwname='foo'), 'USAGE');
+SELECT has_foreign_data_wrapper_privilege(
+    (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'), 'foo', 'USAGE');
+SELECT has_foreign_data_wrapper_privilege('foo', 'USAGE');
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role;
+SELECT has_foreign_data_wrapper_privilege('regress_test_role', 'foo', 'USAGE');
+
+-- has_server_privilege
+SELECT has_server_privilege('regress_test_role',
+    (SELECT oid FROM pg_foreign_server WHERE srvname='s8'), 'USAGE');
+SELECT has_server_privilege('regress_test_role', 's8', 'USAGE');
+SELECT has_server_privilege(
+    (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'),
+    (SELECT oid FROM pg_foreign_server WHERE srvname='s8'), 'USAGE');
+SELECT has_server_privilege(
+    (SELECT oid FROM pg_foreign_server WHERE srvname='s8'), 'USAGE');
+SELECT has_server_privilege(
+    (SELECT oid FROM pg_roles WHERE rolname='regress_test_role'), 's8', 'USAGE');
+SELECT has_server_privilege('s8', 'USAGE');
+GRANT USAGE ON FOREIGN SERVER s8 TO regress_test_role;
+SELECT has_server_privilege('regress_test_role', 's8', 'USAGE');
+REVOKE USAGE ON FOREIGN SERVER s8 FROM regress_test_role;
+
+GRANT USAGE ON FOREIGN SERVER s4 TO regress_test_role;
+DROP USER MAPPING FOR public SERVER s4;
+ALTER SERVER s6 OPTIONS (DROP host, DROP dbname);
+ALTER USER MAPPING FOR regress_test_role SERVER s6 OPTIONS (DROP username);
+ALTER FOREIGN DATA WRAPPER foo LIBRARY 'plpgsql';
+ALTER FOREIGN DATA WRAPPER foo LIBRARY 'default_fdw';
+
+-- Privileges
+SET ROLE unprivileged_role;
+CREATE FOREIGN DATA WRAPPER foobar LIBRARY 'dummy_fdw' LANGUAGE C; -- ERROR
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (gotcha 'true');         -- ERROR
+ALTER FOREIGN DATA WRAPPER foo OWNER TO unprivileged_role;      -- ERROR
+DROP FOREIGN DATA WRAPPER foo;                                  -- ERROR
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role;   -- ERROR
+CREATE SERVER s9 FOREIGN DATA WRAPPER foo;                      -- ERROR
+ALTER SERVER s4 VERSION '0.5';                                  -- ERROR
+ALTER SERVER s4 OWNER TO unprivileged_role;                     -- ERROR
+DROP SERVER s4;                                                 -- ERROR
+GRANT USAGE ON FOREIGN SERVER s4 TO regress_test_role;          -- ERROR
+CREATE USER MAPPING FOR public SERVER s4;                       -- ERROR
+ALTER USER MAPPING FOR regress_test_role SERVER s6 OPTIONS (gotcha 'true'); -- ERROR
+DROP USER MAPPING FOR regress_test_role SERVER s6;              -- ERROR
+RESET ROLE;
+
+GRANT USAGE ON FOREIGN DATA WRAPPER postgresql TO unprivileged_role;
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO unprivileged_role WITH GRANT OPTION;
+SET ROLE unprivileged_role;
+CREATE FOREIGN DATA WRAPPER foobar LIBRARY 'dummy_fdw' LANGUAGE C; -- ERROR
+ALTER FOREIGN DATA WRAPPER foo OPTIONS (gotcha 'true');         -- ERROR
+DROP FOREIGN DATA WRAPPER foo;                                  -- ERROR
+GRANT USAGE ON FOREIGN DATA WRAPPER postgresql TO regress_test_role; -- WARNING
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role;
+CREATE SERVER s9 FOREIGN DATA WRAPPER postgresql;
+ALTER SERVER s6 VERSION '0.5';                                  -- ERROR
+DROP SERVER s6;                                                 -- ERROR
+GRANT USAGE ON FOREIGN SERVER s6 TO regress_test_role;          -- ERROR
+GRANT USAGE ON FOREIGN SERVER s9 TO regress_test_role;
+CREATE USER MAPPING FOR public SERVER s6;                       -- ERROR
+CREATE USER MAPPING FOR public SERVER s9;
+ALTER USER MAPPING FOR regress_test_role SERVER s6 OPTIONS (gotcha 'true'); -- ERROR
+DROP USER MAPPING FOR regress_test_role SERVER s6;              -- ERROR
+RESET ROLE;
+
+REVOKE USAGE ON FOREIGN DATA WRAPPER foo FROM unprivileged_role; -- ERROR
+REVOKE USAGE ON FOREIGN DATA WRAPPER foo FROM unprivileged_role CASCADE;
+SET ROLE unprivileged_role;
+GRANT USAGE ON FOREIGN DATA WRAPPER foo TO regress_test_role;   -- ERROR
+CREATE SERVER s10 FOREIGN DATA WRAPPER foo;                     -- ERROR
+ALTER SERVER s9 VERSION '1.1';
+GRANT USAGE ON FOREIGN SERVER s9 TO regress_test_role;
+CREATE USER MAPPING FOR current_user SERVER s9;
+DROP SERVER s9 CASCADE;
+RESET ROLE;
+CREATE SERVER s9 FOREIGN DATA WRAPPER foo;
+GRANT USAGE ON FOREIGN SERVER s9 TO unprivileged_role;
+SET ROLE unprivileged_role;
+ALTER SERVER s9 VERSION '1.2';                                  -- ERROR
+GRANT USAGE ON FOREIGN SERVER s9 TO regress_test_role;          -- WARNING
+CREATE USER MAPPING FOR current_user SERVER s9;                 -- ERROR
+DROP SERVER s9 CASCADE;                                         -- ERROR
+RESET ROLE;
+
+-- Cleanup
+DROP ROLE regress_test_role;                                -- ERROR
+DROP SERVER s5 CASCADE;
+DROP SERVER st1 CASCADE;
+DROP SERVER st2;
+DROP USER MAPPING FOR regress_test_role SERVER s6;
+DROP FOREIGN DATA WRAPPER foo CASCADE;
+DROP SERVER s8 CASCADE;
+DROP ROLE regress_test_indirect;
+DROP ROLE regress_test_role;
+DROP ROLE unprivileged_role;                                -- ERROR
+REVOKE ALL ON FOREIGN DATA WRAPPER postgresql FROM unprivileged_role;
+DROP ROLE unprivileged_role;
+DROP ROLE regress_test_role2;
+DROP FOREIGN DATA WRAPPER postgresql CASCADE;
+DROP FOREIGN DATA WRAPPER dummy CASCADE;
+\c
+DROP ROLE foreign_data_user;
+
+-- At this point we should have no wrappers, no servers, and no mappings.
+SELECT fdwname, fdwlibrary, fdwoptions FROM pg_foreign_data_wrapper;
+SELECT srvname, srvoptions FROM pg_foreign_server;
+SELECT * FROM pg_user_mapping;