]> granicus.if.org Git - postgresql/commitdiff
Add support for using SQL/MED compliant FOREIGN DATA WRAPPER, SERVER,
authorJoe Conway <mail@joeconway.com>
Sat, 6 Jun 2009 21:27:56 +0000 (21:27 +0000)
committerJoe Conway <mail@joeconway.com>
Sat, 6 Jun 2009 21:27:56 +0000 (21:27 +0000)
and USER MAPPING as method to supply dblink connect parameters. Per
mailing list and PGCon discussions.

contrib/dblink/dblink.c
contrib/dblink/expected/dblink.out
contrib/dblink/sql/dblink.sql
doc/src/sgml/dblink.sgml

index a8d5a6766f37d1f1e48355f2fbf43734713681aa..283b4d0b25e9924dfc2d16075e7cfc1277ce5956 100644 (file)
@@ -8,7 +8,7 @@
  * Darko Prenosil <Darko.Prenosil@finteh.hr>
  * Shridhar Daithankar <shridhar_daithankar@persistent.co.in>
  *
- * $PostgreSQL: pgsql/contrib/dblink/dblink.c,v 1.78 2009/06/02 03:21:56 joe Exp $
+ * $PostgreSQL: pgsql/contrib/dblink/dblink.c,v 1.79 2009/06/06 21:27:56 joe Exp $
  * Copyright (c) 2001-2009, PostgreSQL Global Development Group
  * ALL RIGHTS RESERVED;
  *
@@ -46,6 +46,7 @@
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "foreign/foreign.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
@@ -96,6 +97,8 @@ static char *generate_relation_name(Oid relid);
 static void dblink_connstr_check(const char *connstr);
 static void dblink_security_check(PGconn *conn, remoteConn *rconn);
 static void dblink_res_error(const char *conname, PGresult *res, const char *dblink_context_msg, bool fail);
+static char *get_connect_string(const char *servername);
+static char *escape_param_str(const char *from);
 
 /* Global */
 static remoteConn *pconn = NULL;
@@ -165,7 +168,11 @@ typedef struct remoteConnHashEnt
                        } \
                        else \
                        { \
-                               connstr = conname_or_str; \
+                               connstr = get_connect_string(conname_or_str); \
+                               if (connstr == NULL) \
+                               { \
+                                       connstr = conname_or_str; \
+                               } \
                                dblink_connstr_check(connstr); \
                                conn = PQconnectdb(connstr); \
                                if (PQstatus(conn) == CONNECTION_BAD) \
@@ -210,6 +217,7 @@ PG_FUNCTION_INFO_V1(dblink_connect);
 Datum
 dblink_connect(PG_FUNCTION_ARGS)
 {
+       char       *conname_or_str = NULL;
        char       *connstr = NULL;
        char       *connname = NULL;
        char       *msg;
@@ -220,16 +228,21 @@ dblink_connect(PG_FUNCTION_ARGS)
 
        if (PG_NARGS() == 2)
        {
-               connstr = text_to_cstring(PG_GETARG_TEXT_PP(1));
+               conname_or_str = text_to_cstring(PG_GETARG_TEXT_PP(1));
                connname = text_to_cstring(PG_GETARG_TEXT_PP(0));
        }
        else if (PG_NARGS() == 1)
-               connstr = text_to_cstring(PG_GETARG_TEXT_PP(0));
+               conname_or_str = text_to_cstring(PG_GETARG_TEXT_PP(0));
 
        if (connname)
                rconn = (remoteConn *) MemoryContextAlloc(TopMemoryContext,
                                                                                                  sizeof(remoteConn));
 
+       /* first check for valid foreign data server */
+       connstr = get_connect_string(conname_or_str);
+       if (connstr == NULL)
+               connstr = conname_or_str;
+
        /* check password in connection string if not superuser */
        dblink_connstr_check(connstr);
        conn = PQconnectdb(connstr);
@@ -2353,3 +2366,86 @@ dblink_res_error(const char *conname, PGresult *res, const char *dblink_context_
                 errcontext("Error occurred on dblink connection named \"%s\": %s.",
                                        dblink_context_conname, dblink_context_msg)));
 }
+
+/*
+ * Obtain connection string for a foreign server
+ */
+static char *
+get_connect_string(const char *servername)
+{
+       ForeignServer      *foreign_server = NULL;
+       UserMapping                *user_mapping;
+       ListCell                   *cell;
+       StringInfo                      buf = makeStringInfo();
+       ForeignDataWrapper *fdw;
+       AclResult                       aclresult;
+
+       /* first gather the server connstr options */
+       if (strlen(servername) < NAMEDATALEN)
+               foreign_server = GetForeignServerByName(servername, true);
+
+       if (foreign_server)
+       {
+               Oid             serverid = foreign_server->serverid;
+               Oid             fdwid = foreign_server->fdwid;
+               Oid             userid = GetUserId();
+
+               user_mapping = GetUserMapping(userid, serverid);
+               fdw     = GetForeignDataWrapper(fdwid);
+
+               /* Check permissions, user must have usage on the server. */
+               aclresult = pg_foreign_server_aclcheck(serverid, userid, ACL_USAGE);
+               if (aclresult != ACLCHECK_OK)
+                       aclcheck_error(aclresult, ACL_KIND_FOREIGN_SERVER, foreign_server->servername);
+
+               foreach (cell, fdw->options)
+               {
+                       DefElem            *def = lfirst(cell);
+
+                       appendStringInfo(buf, "%s='%s' ", def->defname,
+                                                        escape_param_str(strVal(def->arg)));
+               }
+
+               foreach (cell, foreign_server->options)
+               {
+                       DefElem            *def = lfirst(cell);
+       
+                       appendStringInfo(buf, "%s='%s' ", def->defname,
+                                                        escape_param_str(strVal(def->arg)));
+               }
+       
+               foreach (cell, user_mapping->options)
+               {
+       
+                       DefElem            *def = lfirst(cell);
+       
+                       appendStringInfo(buf, "%s='%s' ", def->defname,
+                                                        escape_param_str(strVal(def->arg)));
+               }
+
+               return buf->data;
+       }
+       else
+               return NULL;
+}
+
+/*
+ * Escaping libpq connect parameter strings.
+ *
+ * Replaces "'" with "\'" and "\" with "\\".
+ */
+static char *
+escape_param_str(const char *str)
+{
+       const char         *cp;
+       StringInfo              buf = makeStringInfo();
+
+       for (cp = str; *cp; cp++)
+       {
+               if (*cp == '\\' || *cp == '\'')
+                       appendStringInfoChar(buf, '\\');
+               appendStringInfoChar(buf, *cp);
+       }
+
+       return buf->data;
+}
index f2eb6aa5e2fc65b5fd8f5b17455b1e544b10df51..102444d88cf322f374181523639981d5e550a69f 100644 (file)
@@ -784,3 +784,46 @@ SELECT dblink_disconnect('dtest1');
  OK
 (1 row)
 
+-- test foreign data wrapper functionality
+CREATE USER dblink_regression_test;
+CREATE FOREIGN DATA WRAPPER postgresql;
+CREATE SERVER fdtest FOREIGN DATA WRAPPER postgresql OPTIONS (dbname 'contrib_regression');
+CREATE USER MAPPING FOR public SERVER fdtest;
+GRANT USAGE ON FOREIGN SERVER fdtest TO dblink_regression_test;
+GRANT EXECUTE ON FUNCTION dblink_connect_u(text, text) TO dblink_regression_test;
+\set ORIGINAL_USER :USER
+\c - dblink_regression_test
+-- should fail
+SELECT dblink_connect('myconn', 'fdtest');
+ERROR:  password is required
+DETAIL:  Non-superusers must provide a password in the connection string.
+-- should succeed
+SELECT dblink_connect_u('myconn', 'fdtest');
+ dblink_connect_u 
+------------------
+ OK
+(1 row)
+
+SELECT * FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]);
+ a  | b |       c       
+----+---+---------------
+  0 | a | {a0,b0,c0}
+  1 | b | {a1,b1,c1}
+  2 | c | {a2,b2,c2}
+  3 | d | {a3,b3,c3}
+  4 | e | {a4,b4,c4}
+  5 | f | {a5,b5,c5}
+  6 | g | {a6,b6,c6}
+  7 | h | {a7,b7,c7}
+  8 | i | {a8,b8,c8}
+  9 | j | {a9,b9,c9}
+ 10 | k | {a10,b10,c10}
+(11 rows)
+
+\c - :ORIGINAL_USER
+REVOKE USAGE ON FOREIGN SERVER fdtest FROM dblink_regression_test;
+REVOKE EXECUTE ON FUNCTION dblink_connect_u(text, text) FROM dblink_regression_test;
+DROP USER dblink_regression_test;
+DROP USER MAPPING FOR public SERVER fdtest;
+DROP SERVER fdtest;
+DROP FOREIGN DATA WRAPPER postgresql;
index 7e91e9c6db4e0f59f5a81516add9e21a15479728..a8190c6564e1d5e3beb6b018a6cb4e3486c861a6 100644 (file)
@@ -364,3 +364,28 @@ SELECT * from
 SELECT dblink_cancel_query('dtest1');
 SELECT dblink_error_message('dtest1');
 SELECT dblink_disconnect('dtest1');
+
+-- test foreign data wrapper functionality
+CREATE USER dblink_regression_test;
+
+CREATE FOREIGN DATA WRAPPER postgresql;
+CREATE SERVER fdtest FOREIGN DATA WRAPPER postgresql OPTIONS (dbname 'contrib_regression');
+CREATE USER MAPPING FOR public SERVER fdtest;
+GRANT USAGE ON FOREIGN SERVER fdtest TO dblink_regression_test;
+GRANT EXECUTE ON FUNCTION dblink_connect_u(text, text) TO dblink_regression_test;
+
+\set ORIGINAL_USER :USER
+\c - dblink_regression_test
+-- should fail
+SELECT dblink_connect('myconn', 'fdtest');
+-- should succeed
+SELECT dblink_connect_u('myconn', 'fdtest');
+SELECT * FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]);
+
+\c - :ORIGINAL_USER
+REVOKE USAGE ON FOREIGN SERVER fdtest FROM dblink_regression_test;
+REVOKE EXECUTE ON FUNCTION dblink_connect_u(text, text) FROM dblink_regression_test;
+DROP USER dblink_regression_test;
+DROP USER MAPPING FOR public SERVER fdtest;
+DROP SERVER fdtest;
+DROP FOREIGN DATA WRAPPER postgresql;
index 7feb534cdbb67f8e035d687281c114e3222988ac..06fcc8cee8bfd7c32de384864e876db45490b0a9 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/dblink.sgml,v 1.6 2008/11/12 15:52:44 petere Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/dblink.sgml,v 1.7 2009/06/06 21:27:56 joe Exp $ -->
 
 <sect1 id="dblink">
  <title>dblink</title>
     only one unnamed connection is permitted at a time.  The connection
     will persist until closed or until the database session is ended.
    </para>
+
+   <para>
+    The connection string may also be the name of an existing foreign
+    server that utilizes the postgresql_fdw foreign data wrapper library.
+    See the example below, as well as the following:
+    <simplelist type="inline">
+     <member><xref linkend="sql-createforeigndatawrapper" endterm="sql-createforeigndatawrapper-title"></member>
+     <member><xref linkend="sql-createserver" endterm="sql-createserver-title"></member>
+     <member><xref linkend="sql-createusermapping" endterm="sql-createusermapping-title"></member>
+    </simplelist>
+   </para>
+
   </refsect1>
 
   <refsect1>
  ----------------
   OK
  (1 row)
+
+ -- FOREIGN DATA WRAPPER functionality
+ -- Note: local connection must require password authentication for this to work properly
+ --       Otherwise, you will receive the following error from dblink_connect():
+ --       ----------------------------------------------------------------------
+ --       ERROR:  password is required
+ --       DETAIL:  Non-superuser cannot connect if the server does not request a password.
+ --       HINT:  Target server's authentication method must be changed.
+ CREATE USER dblink_regression_test WITH PASSWORD 'secret';
+ CREATE FOREIGN DATA WRAPPER postgresql;
+ CREATE SERVER fdtest FOREIGN DATA WRAPPER postgresql OPTIONS (hostaddr '127.0.0.1', dbname 'contrib_regression');
+
+ CREATE USER MAPPING FOR dblink_regression_test SERVER fdtest OPTIONS (user 'dblink_regression_test', password 'secret');
+ GRANT USAGE ON FOREIGN SERVER fdtest TO dblink_regression_test;
+ GRANT SELECT ON TABLE foo TO dblink_regression_test;
+
+ \set ORIGINAL_USER :USER
+ \c - dblink_regression_test
+ SELECT dblink_connect('myconn', 'fdtest');
+  dblink_connect 
+ ----------------
+  OK
+ (1 row)
+
+ SELECT * FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]);
+  a  | b |       c       
+ ----+---+---------------
+   0 | a | {a0,b0,c0}
+   1 | b | {a1,b1,c1}
+   2 | c | {a2,b2,c2}
+   3 | d | {a3,b3,c3}
+   4 | e | {a4,b4,c4}
+   5 | f | {a5,b5,c5}
+   6 | g | {a6,b6,c6}
+   7 | h | {a7,b7,c7}
+   8 | i | {a8,b8,c8}
+   9 | j | {a9,b9,c9}
+  10 | k | {a10,b10,c10}
+ (11 rows)
+
+ \c - :ORIGINAL_USER
+ REVOKE USAGE ON FOREIGN SERVER fdtest FROM dblink_regression_test;
+ REVOKE SELECT ON TABLE foo FROM  dblink_regression_test;
+ DROP USER MAPPING FOR dblink_regression_test SERVER fdtest;
+ DROP USER dblink_regression_test;
+ DROP SERVER fdtest;
+ DROP FOREIGN DATA WRAPPER postgresql;
    </programlisting>
   </refsect1>
  </refentry>