diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index a8d5a6766f..283b4d0b25 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -8,7 +8,7 @@ * Darko Prenosil * Shridhar Daithankar * - * $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; +} diff --git a/contrib/dblink/expected/dblink.out b/contrib/dblink/expected/dblink.out index f2eb6aa5e2..102444d88c 100644 --- a/contrib/dblink/expected/dblink.out +++ b/contrib/dblink/expected/dblink.out @@ -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; diff --git a/contrib/dblink/sql/dblink.sql b/contrib/dblink/sql/dblink.sql index 7e91e9c6db..a8190c6564 100644 --- a/contrib/dblink/sql/dblink.sql +++ b/contrib/dblink/sql/dblink.sql @@ -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; diff --git a/doc/src/sgml/dblink.sgml b/doc/src/sgml/dblink.sgml index 7feb534cdb..06fcc8cee8 100644 --- a/doc/src/sgml/dblink.sgml +++ b/doc/src/sgml/dblink.sgml @@ -1,4 +1,4 @@ - + dblink @@ -42,6 +42,18 @@ only one unnamed connection is permitted at a time. The connection will persist until closed or until the database session is ended. + + + 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: + + + + + + + @@ -113,6 +125,53 @@ ---------------- 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;