Thread: SQL/MED compatible connection manager

SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Howdy,

Currently pl/proxy, dblink, DBI-link etc. each have their own remote connection
info management infrastructure (or none at all). It would certainly help if
they could use a common connection database -- with access control, pg_dump
support, etc. There have been hints that a SQL/MED compatible connection
manager would be desirable:
http://archives.postgresql.org/pgsql-hackers/2008-09/msg00314.php
http://archives.postgresql.org/pgsql-hackers/2008-09/msg00909.php

So the proposal is to implement a small subset of SQL/MED to cope with
connection info management -- connection manager. This will only manage the
connection metadata and provide the required system catalogs and commands for
maintaining them. The actual connection management (open/close etc.) is still
handled by the client modules.

I have put together a draft that describes a possible implementation:
http://wiki.postgresql.org/wiki/SqlMedConnectionManager

Tons of details have been omitted, but should be enough to start discussion.
What do you think, does this sound usable? Suggestions, objections?

thanks,
Martin



Re: SQL/MED compatible connection manager

From
"Jonah H. Harris"
Date:
On Mon, Oct 27, 2008 at 10:06 AM, Martin Pihlak <martin.pihlak@gmail.com> wrote:
> So the proposal is to implement a small subset of SQL/MED to cope with
> connection info management -- connection manager. This will only manage the
> connection metadata and provide the required system catalogs and commands for
> maintaining them. The actual connection management (open/close etc.) is still
> handled by the client modules.

Per SQL:2008, there are no expected changes to SQL/MED from SQL:2003.
As such, two weeks ago, I completed a full review of SQL/MED and am
planning to fully implement it for 8.5.  Currently, I'm working on a
proof of concept and have created a SQL/MED access method (sqlmed) as
well as started implementing the FDW API and hooks into the optimizer
to support remote capabilities, costing, and predicate pushdown.  The
first wrappers I intend to support are ODBC and

This is a large project, and I'm certainly open to assistance :)

-- 
Jonah H. Harris, Senior DBA
myYearbook.com


Re: SQL/MED compatible connection manager

From
"Jonah H. Harris"
Date:
On Mon, Oct 27, 2008 at 10:35 AM, Jonah H. Harris
<jonah.harris@gmail.com> wrote:
> The first wrappers I intend to support are ODBC and

Damn multiple windows :)

The first wrappers I intend to support are ODBC and CSV/fixed-width text.

-- 
Jonah H. Harris, Senior DBA
myYearbook.com


Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
> Per SQL:2008, there are no expected changes to SQL/MED from SQL:2003.
> As such, two weeks ago, I completed a full review of SQL/MED and am
> planning to fully implement it for 8.5.  Currently, I'm working on a
> proof of concept and have created a SQL/MED access method (sqlmed) as
> well as started implementing the FDW API and hooks into the optimizer
> to support remote capabilities, costing, and predicate pushdown.  The
> first wrappers I intend to support are ODBC and

Cool. Have you published some notes on it (wiki etc)?

> 
> This is a large project, and I'm certainly open to assistance :)
> 

It certainly is an undertaking :) I'm mostly interested in the connection
management -- so hopefully I can help there.

regards,
Martin



Re: SQL/MED compatible connection manager

From
"Jonah H. Harris"
Date:
On Mon, Oct 27, 2008 at 11:31 AM, Martin Pihlak <martin.pihlak@gmail.com> wrote:
> Cool. Have you published some notes on it (wiki etc)?

Not yet.  Discussed it a little on irc, but nothing substantial.  I'll
look at updating the Wiki hopefully today.

> It certainly is an undertaking :) I'm mostly interested in the connection
> management -- so hopefully I can help there.

That would be awesome!

-- 
Jonah H. Harris, Senior DBA
myYearbook.com


Re: SQL/MED compatible connection manager

From
Chris Browne
Date:
martin.pihlak@gmail.com (Martin Pihlak) writes:
> Tons of details have been omitted, but should be enough to start discussion.
> What do you think, does this sound usable? Suggestions, objections?

Slony-I does some vaguely similar stuff in its handling of "connection paths"; here's the schema:

create table @NAMESPACE@.sl_path (pa_server            int4,pa_client            int4,pa_conninfo            text NOT
NULL,pa_connretry       int4,
 
CONSTRAINT "sl_path-pkey"    PRIMARY KEY (pa_server, pa_client),CONSTRAINT "pa_server-no_id-ref"    FOREIGN KEY
(pa_server)   REFERENCES @NAMESPACE@.sl_node (no_id),CONSTRAINT "pa_client-no_id-ref"    FOREIGN KEY (pa_client)
REFERENCES@NAMESPACE@.sl_node (no_id)
 
) WITHOUT OIDS;
comment on table @NAMESPACE@.sl_path is 'Holds connection information for the paths between nodes, and the
synchronisationdelay';
 
comment on column @NAMESPACE@.sl_path.pa_server is 'The Node ID # (from sl_node.no_id) of the data source';
comment on column @NAMESPACE@.sl_path.pa_client is 'The Node ID # (from sl_node.no_id) of the data target';
comment on column @NAMESPACE@.sl_path.pa_conninfo is 'The PostgreSQL connection string used to connect to the source
node.';
comment on column @NAMESPACE@.sl_path.pa_connretry is 'The synchronisation delay, in seconds';

I wouldn't be surprised to find there being some value in using
something like SQL/MED.

One detail I'll point out, that I'm noticing from an application I'm
working on right now.  We might want to have something like a "db
connection" data type; here's a prototype I put together:

slonyregress1=# create type dbconn as (port integer, dbname text, username text, password text, ssl boolean);
CREATE TYPE
slonyregress1=# create table dbconns (id serial primary key, db dbconn);
NOTICE: CREATE TABLE will create implicit sequence "dbconns_id_seq" for serial column "dbconns.id"
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "dbconns_pkey" for table "dbconns"
CREATE TABLE
slonyregress1=# insert into dbconns (db) values ((5432, 'slonyregress1', 'slony', 'secret!', 'true'));
INSERT 0 1
slonyregress1=# select * from dbconns;id | db 
----+-------------------------------------- 1 | (5432,slonyregress1,slony,secret!,t)(1 row)

I'm not certain that this is forcibly the right representation, but I
think it is possible that we'd want a finer-grained representation
than merely a connection string.
-- 
(reverse (concatenate 'string "ofni.sesabatadxunil" "@" "enworbbc"))
http://linuxdatabases.info/info/finances.html
"DTDs are  not common knowledge  because programming students  are not
taught markup.  A markup language is not a programming language."
-- Peter Flynn <silmaril@m-net.arbornet.org>


Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Chris Browne wrote:

> Slony-I does some vaguely similar stuff in its handling of "connection paths"; here's the schema:
> 
> create table @NAMESPACE@.sl_path (
>     pa_server            int4,
>     pa_client            int4,
>     pa_conninfo            text NOT NULL,
>     pa_connretry        int4,
[snip ...]
> I wouldn't be surprised to find there being some value in using
> something like SQL/MED.
> 

Here the pa_conninfo could be replaced with the connection name (actually
SERVER). For the complete connection definition a USER MAPPING (eg. remote
username and password) is also needed. But that can be fetched by the
connection connection lookup function

> One detail I'll point out, that I'm noticing from an application I'm
> working on right now.  We might want to have something like a "db
> connection" data type; here's a prototype I put together:
> 

> slonyregress1=# create type dbconn as (port integer, dbname text, username text, password text, ssl boolean);
> CREATE TYPE
[snip]
> slonyregress1=# select * from dbconns;
>  id | db 
> ----+--------------------------------------
>   1 | (5432,slonyregress1,slony,secret!,t)
>  (1 row)
> 
> I'm not certain that this is forcibly the right representation, but I
> think it is possible that we'd want a finer-grained representation
> than merely a connection string.

Yes -- the server, user mapping and FDW all take generic options. Some of them
might be part of the connect string, others could specify some hints of how the
connection should be handled (driver options etc). DBD-Excel has a particularly
nasty example of those. A fixed type would not be able to cover all of them.
This is where the SQL/MED stuff can help - all of this complexity can be reduced
to a single name. Though it adds the additional step of doing the lookup.

The dbconns example could be written like this:

test=# create table dbconns (id serial primary key, db regserver);
...
test=# insert into dbconns (db) values ('test');
INSERT 0 1
test=# select * from dbconns;id |     db
----+------------- 1 | public.test
(1 row)

And then for the connection details:

test=# select * from pg_get_remote_connection_info('test'); option  | value
----------+--------host     | /tmpport     | 6543dbname   | foousername | bobpassword | secret
(5 rows)

This assumes that there is a server "public.test" and a user mapping for
the session user. The option/value pairs are outputted by the "dummy" FDW
that just dumps the generic options for the server and user mapping. A
"smart" FDW could turn this into just a connection string. Still, there
probably should be a set of functions for accessing the raw options/value
pairs as well

regards,
Martin




Re: SQL/MED compatible connection manager

From
Hannu Krosing
Date:
On Mon, 2008-10-27 at 16:50 -0400, Chris Browne wrote:
> martin.pihlak@gmail.com (Martin Pihlak) writes:
> > Tons of details have been omitted, but should be enough to start discussion.
> > What do you think, does this sound usable? Suggestions, objections?
> 
> Slony-I does some vaguely similar stuff in its handling of "connection paths"; here's the schema:

I think the whole issue was initially raised by "insecurity", as dblink
conrib module exposed connection strings to all users, and SQL/MED was
seen as a "standard" way to hide it.

The simple credentials hiding could of course be achieved by having
something similar to pg_user/pg_shadow and some SECURITY DEFINER
functions for actually opening the connections, but probably it seemed
easier to at least base it on standards, so we can actually start with

pg_remote_server table public
pg_user_mapping_shadow table (restricted)/ pg_user_mapping view(public)

and some functions with proper grants to match the subset that Martin
outlined in http://wiki.postgresql.org/wiki/SqlMedConnectionManager

if we've got that working, then we could move to massaging it into the
parser to provide standard SQL/MED syntax.

so I think that first we should agree on functionality and get the few
system (?) tables and functions done, and worry about parser changes
once the actual functionality is field tested.

------------------------------------------
Hannu Krosing   http://www.2ndQuadrant.com
PostgreSQL Scalability and Availability   Services, Consulting and Training




Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
> I have put together a draft that describes a possible implementation:
> http://wiki.postgresql.org/wiki/SqlMedConnectionManager
>

I'll update this with an experimental patch. This implements:
* System catalogs for FDW, SERVER and USER MAPPING
* regserver data type for servers (convert from text to oid etc).
* FDW placeholders as shared libraries -- currently provides dummy
  and pgsql wrappers.
* Connection lookup via FDW.
* SQL interface to the lookup function.

There is an example of how all this works at:
http://wiki.postgresql.org/wiki/SqlMedConnectionManager#Current_implementation

I was hoping to get the internal features done by the start of November
Commitfest. But right now this seems unlikely. Nevertheless, I'm all ears
for suggestions and criticism.

PS. Jonah, does this somehow coincide with your approach to FDW-s?

regards,
Martin


Attachment

Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Here is an updated patch. I'm also submitting this to November Commitfest -
I believe it is possible to get this into shape for 8.4. This is still a WIP
but I really need a review before moving on with the syntax, pg_dump support
etc.

Currently the system catalogs and user accessible connection lookup function
have been implemented. Ships with 2 FOREIGN DATA WRAPPERS -- dummy and pgsql.
It is possible to define connections by inserting directly into the catalogs:

insert into pg_catalog.pg_foreign_server
    select 'foo', 2200, 10, oid, null,
            '{host=/tmp,port=6543,dbname=foo}'::text[]
    from     pg_foreign_data_wrapper
    where     fdwname='default';

insert into pg_catalog.pg_foreign_user_mapping
    select    10, oid, '{user=bob,password=secret}'::text[]
    from     pg_foreign_server
    where     srvname='foo' ;

select * from pg_get_remote_connection_info('foo');

  option  | value
----------+--------
 host     | /tmp
 port     | 6543
 dbname   | foo
 user     | bob
 password | secret
(5 rows)

regards,
Martin


Attachment

Re: SQL/MED compatible connection manager

From
Hannu Krosing
Date:
On Fri, 2008-10-31 at 17:49 +0200, Martin Pihlak wrote:
> Here is an updated patch. I'm also submitting this to November Commitfest -
> I believe it is possible to get this into shape for 8.4. This is still a WIP
> but I really need a review before moving on with the syntax, pg_dump support
> etc.

Any hope of getting pl/proxy using this submitted for 8.4  ?

------------------------------------------
Hannu Krosing   http://www.2ndQuadrant.com
PostgreSQL Scalability and Availability   Services, Consulting and Training



Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Attached is next revision of the connection manager, this is now nearly
feature complete -- the syntax is there, privileges are implemented.
Should apply cleanly to HEAD and pass regression. There are some usage
examples at: http://wiki.postgresql.org/wiki/SqlMedConnectionManager#Examples

Some issues that come to mind:
- Object naming: probably should drop "foreign_" prefix from user mapping
  and server. This would leave us with pg_user_mapping and pg_server.
  Maybe add a _shadow suffix to the hidden version of pg_user_mapping.
- MAPPING became a reserved keyword, this is not good (unreserved causes
  shift/reduce conflicts).
- There is some more grammar hacking to be done - ALTER handling is not
  complete (allows nop statements, impossible to reset options, cannot use
  default as FDW name, ...).
- System catalog and connection lookup function privileges are revoked from
  public in system_views.sql, is there an alternative?
- Worry about FDW library function pointers getting stale (library reloads)
- Do we need to support copyObject/equal for the fdw/server/user mapping
  parse nodes?

Things to do:
- internal cleanup - add verbose commentary where needed, cleanup error
  reporting.
- documentation
- more thorough regression tests
- psql support (tab completion, help, \dX commands)
- pg_dump support
- ecpg support?

regards,
Martin


Attachment

Re: SQL/MED compatible connection manager

From
Tom Lane
Date:
Martin Pihlak <martin.pihlak@gmail.com> writes:
> - Do we need to support copyObject/equal for the fdw/server/user mapping
>   parse nodes?

Yes.
        regards, tom lane


Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Here's another revision of the connection manager. This adds:
 * reference documentation
 * psql, tab-completion, \dw, \dr, \dm commands.
 * pg_dump support

Still todo:
 * more comprehensive regression tests
 * introductory documentation
 * dblink support

I hope to finish these items during next week, and remove the WIP
status then.

regards,
Martin


Attachment

Re: SQL/MED compatible connection manager

From
Peter Eisentraut
Date:
Martin Pihlak wrote:
> Here's another revision of the connection manager. This adds:
>  * reference documentation
>  * psql, tab-completion, \dw, \dr, \dm commands.
>  * pg_dump support
> 
> Still todo:
>  * more comprehensive regression tests
>  * introductory documentation
>  * dblink support
> 
> I hope to finish these items during next week, and remove the WIP
> status then.

Looks very good, please continue.

Most of this is straight out of the standard, so there isn't much to 
discuss on the interfaces.  I have two small things to wonder about:

On your wiki page, you have this example:

CREATE SERVER userdb TYPE 'plproxy_cluster'    FOREIGN DATA WRAPPER plproxy    OPTIONS (
server='dbname=userdb_p0host=127.0.0.1 port=6000',        server='dbname=userdb_p1 host=127.0.0.1 port=6000',
server='dbname=userdb_p2host=127.0.0.1 port=6000',        server='dbname=userdb_p3 host=127.0.0.1 port=6000',
connection_lifetime=3600   );
 

If I read this right, SQL/MED requires option names to be unique for a 
server.  To this needs to be rethought.

Do we really need the function pg_get_remote_connection_info()?  This 
could be done directly with the information schema.  E.g., your example

SELECT * FROM pg_get_remote_connection_info('userdb');

appears to be the same as

SELECT option_name, option_value  FROM information_schema.foreign_server_options  WHERE foreign_server_name =
'userdb';

This view also appears to have the necessary access control provisions.

And similarly, pg_get_user_mapping_options() is equivalent to 
information_schema.user_mapping_options.


Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Peter Eisentraut wrote:
> Looks very good, please continue.
> 
Thanks, will do :)

> On your wiki page, you have this example:
> 
> CREATE SERVER userdb TYPE 'plproxy_cluster'
>     FOREIGN DATA WRAPPER plproxy
>     OPTIONS (
>         server='dbname=userdb_p0 host=127.0.0.1 port=6000',
>         server='dbname=userdb_p1 host=127.0.0.1 port=6000',
[snip]
> If I read this right, SQL/MED requires option names to be unique for a
> server.  To this needs to be rethought.
> 

Indeed, seems that I somehow managed to miss that. Additionally, according
to the standard the options should be specified as <name> <value>, instead
of <name> = <value>. Plus the possibility to alter individual options.
I'll look into that.

Updated the wiki to use unique option names.

> Do we really need the function pg_get_remote_connection_info()?  This
> could be done directly with the information schema.  E.g., your example
> 
> SELECT * FROM pg_get_remote_connection_info('userdb');

The purpouse of the connection lookup function is to compose the the actual
connection string from generic options (adds user mapping if needed). This
aims to make it easier for the clients to perform connection lookups. The
idea is that the connection string should be composed by the foreign data
wrapper, instead of letting each client have it's own interpretation of the
options. Though, it is still possible to query the options directly.

> And similarly, pg_get_user_mapping_options() is equivalent to
> information_schema.user_mapping_options.
> 

Hmm, the options are stored as text[], these need to be transformed to be
usable in the views. Seems that additional functions for foreign data wrapper
and server options are also needed. Will add those, along with the
information_schema views.

regards,
Martin



Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Peter Eisentraut wrote:
> If I read this right, SQL/MED requires option names to be unique for a
> server.  To this needs to be rethought.
>

Attached is another revision of the connection manager, notable changes:
* Generic options are now standard compliant -- no duplicate option names,
  possibility to alter individual options.
* information_schema views, added to the end of the file, using chapter
  numbers from part 5 of the standard.  Qualified names are used in fdw
  and server names unless directly visible.
* Added documentation for the connection lookup functions in "System
  Administration Functions". Also documented the new system catalogs.
* Example dblink implementation. Added functions dblink_connect_s,
  dblink_exec_s, dblink_s that operate on predefined foreign servers.
  No documentation or regression tests at the moment. Some examples at:
  http://wiki.postgresql.org/wiki/SqlMedConnectionManager#dblink

I'll also change the commitfest status to "Pending review" -- the features
are complete and I'm not actively working on the code.

regards,
Martin

*** a/contrib/dblink/dblink.c
--- b/contrib/dblink/dblink.c
***************
*** 46,51 ****
--- 46,52 ----
  #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"
***************
*** 77,83 **** typedef struct remoteConn
  /*
   * Internal declarations
   */
! static Datum dblink_record_internal(FunctionCallInfo fcinfo, bool is_async, bool do_get);
  static remoteConn *getConnectionByName(const char *name);
  static HTAB *createConnHash(void);
  static void createNewConnection(const char *name, remoteConn * rconn);
--- 78,85 ----
  /*
   * Internal declarations
   */
! static Datum dblink_record_internal(FunctionCallInfo fcinfo, bool is_async, bool do_get, bool use_server);
! static Datum dblink_exec_internal(FunctionCallInfo fcinfo, bool use_server);
  static remoteConn *getConnectionByName(const char *name);
  static HTAB *createConnHash(void);
  static void createNewConnection(const char *name, remoteConn * rconn);
***************
*** 96,101 **** static char *generate_relation_name(Oid relid);
--- 98,104 ----
  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);

  /* Global */
  static remoteConn *pconn = NULL;
***************
*** 165,171 **** typedef struct remoteConnHashEnt
              } \
              else \
              { \
!                 connstr = conname_or_str; \
                  dblink_connstr_check(connstr); \
                  conn = PQconnectdb(connstr); \
                  if (PQstatus(conn) == CONNECTION_BAD) \
--- 168,177 ----
              } \
              else \
              { \
!                 if (use_server) \
!                     connstr = get_connect_string(conname_or_str); \
!                 else \
!                     connstr = conname_or_str; \
                  dblink_connstr_check(connstr); \
                  conn = PQconnectdb(connstr); \
                  if (PQstatus(conn) == CONNECTION_BAD) \
***************
*** 204,209 **** typedef struct remoteConnHashEnt
--- 210,246 ----
      } while (0)

  /*
+  * Create a persistent connection to another database - predefined
+  * foreign server
+  */
+ PG_FUNCTION_INFO_V1(dblink_connect_server);
+ Datum
+ dblink_connect_server(PG_FUNCTION_ARGS)
+ {
+     char       *servername = NULL;
+     char       *connname = NULL;
+     char       *connstr = NULL;
+
+     if (PG_NARGS() == 2)
+     {
+         servername = text_to_cstring(PG_GETARG_TEXT_PP(1));
+         connname = text_to_cstring(PG_GETARG_TEXT_PP(0));
+     }
+     else if (PG_NARGS() == 1)
+         servername = text_to_cstring(PG_GETARG_TEXT_PP(0));
+
+     connstr = get_connect_string(servername);
+
+     if (!connname)
+         return DirectFunctionCall1(dblink_connect,
+                                    CStringGetTextDatum(connstr));
+
+     return DirectFunctionCall2(dblink_connect,
+                                CStringGetTextDatum(connname),
+                                CStringGetTextDatum(connstr));
+ }
+
+ /*
   * Create a persistent connection to another database
   */
  PG_FUNCTION_INFO_V1(dblink_connect);
***************
*** 689,713 **** PG_FUNCTION_INFO_V1(dblink_record);
  Datum
  dblink_record(PG_FUNCTION_ARGS)
  {
!     return dblink_record_internal(fcinfo, false, false);
  }

  PG_FUNCTION_INFO_V1(dblink_send_query);
  Datum
  dblink_send_query(PG_FUNCTION_ARGS)
  {
!     return dblink_record_internal(fcinfo, true, false);
  }

  PG_FUNCTION_INFO_V1(dblink_get_result);
  Datum
  dblink_get_result(PG_FUNCTION_ARGS)
  {
!     return dblink_record_internal(fcinfo, true, true);
  }

  static Datum
! dblink_record_internal(FunctionCallInfo fcinfo, bool is_async, bool do_get)
  {
      FuncCallContext *funcctx;
      TupleDesc    tupdesc = NULL;
--- 726,757 ----
  Datum
  dblink_record(PG_FUNCTION_ARGS)
  {
!     return dblink_record_internal(fcinfo, false, false, false);
! }
!
! PG_FUNCTION_INFO_V1(dblink_record_server);
! Datum
! dblink_record_server(PG_FUNCTION_ARGS)
! {
!     return dblink_record_internal(fcinfo, false, false, true);
  }

  PG_FUNCTION_INFO_V1(dblink_send_query);
  Datum
  dblink_send_query(PG_FUNCTION_ARGS)
  {
!     return dblink_record_internal(fcinfo, true, false, false);
  }

  PG_FUNCTION_INFO_V1(dblink_get_result);
  Datum
  dblink_get_result(PG_FUNCTION_ARGS)
  {
!     return dblink_record_internal(fcinfo, true, true, false);
  }

  static Datum
! dblink_record_internal(FunctionCallInfo fcinfo, bool is_async, bool do_get, bool use_server)
  {
      FuncCallContext *funcctx;
      TupleDesc    tupdesc = NULL;
***************
*** 1102,1111 **** dblink_error_message(PG_FUNCTION_ARGS)
--- 1146,1168 ----
  /*
   * Execute an SQL non-SELECT command
   */
+ PG_FUNCTION_INFO_V1(dblink_exec_server);
+ Datum
+ dblink_exec_server(PG_FUNCTION_ARGS)
+ {
+     return dblink_exec_internal(fcinfo, true);
+ }
+
  PG_FUNCTION_INFO_V1(dblink_exec);
  Datum
  dblink_exec(PG_FUNCTION_ARGS)
  {
+     return dblink_exec_internal(fcinfo, false);
+ }
+
+ static Datum
+ dblink_exec_internal(FunctionCallInfo fcinfo, bool use_server)
+ {
      char       *msg;
      PGresult   *res = NULL;
      text       *sql_cmd_status = NULL;
***************
*** 2358,2360 **** dblink_res_error(const char *conname, PGresult *res, const char *dblink_context_
--- 2415,2442 ----
           errcontext("Error occurred on dblink connection named \"%s\": %s.",
                      dblink_context_conname, dblink_context_msg)));
  }
+
+ /*
+  * Obtain the connection string for the foreign server.
+  */
+ static char *
+ get_connect_string(const char *servername)
+ {
+     Oid            serverid;
+     ListCell   *cell;
+
+     /* look up the connection string */
+     serverid = ForeignServerNameGetServerid(stringToQualifiedNameList(servername), false);
+     foreach (cell, GetRemoteConnectionInfo(serverid, GetUserId()))
+     {
+         DefElem *def = lfirst(cell);
+
+         if (strcmp(def->defname, "datasource") == 0)
+             return strVal(def->arg);
+     }
+
+     ereport(ERROR,
+             (errcode(ERRCODE_UNDEFINED_PARAMETER),
+              errmsg("could not find \"datasource\" in connection definition")));
+     return NULL;
+ }
*** a/contrib/dblink/dblink.h
--- b/contrib/dblink/dblink.h
***************
*** 40,50 ****
--- 40,52 ----
   * External declarations
   */
  extern Datum dblink_connect(PG_FUNCTION_ARGS);
+ extern Datum dblink_connect_server(PG_FUNCTION_ARGS);
  extern Datum dblink_disconnect(PG_FUNCTION_ARGS);
  extern Datum dblink_open(PG_FUNCTION_ARGS);
  extern Datum dblink_close(PG_FUNCTION_ARGS);
  extern Datum dblink_fetch(PG_FUNCTION_ARGS);
  extern Datum dblink_record(PG_FUNCTION_ARGS);
+ extern Datum dblink_record_server(PG_FUNCTION_ARGS);
  extern Datum dblink_send_query(PG_FUNCTION_ARGS);
  extern Datum dblink_get_result(PG_FUNCTION_ARGS);
  extern Datum dblink_get_connections(PG_FUNCTION_ARGS);
***************
*** 52,57 **** extern Datum dblink_is_busy(PG_FUNCTION_ARGS);
--- 54,60 ----
  extern Datum dblink_cancel_query(PG_FUNCTION_ARGS);
  extern Datum dblink_error_message(PG_FUNCTION_ARGS);
  extern Datum dblink_exec(PG_FUNCTION_ARGS);
+ extern Datum dblink_exec_server(PG_FUNCTION_ARGS);
  extern Datum dblink_get_pkey(PG_FUNCTION_ARGS);
  extern Datum dblink_build_sql_insert(PG_FUNCTION_ARGS);
  extern Datum dblink_build_sql_delete(PG_FUNCTION_ARGS);
*** a/contrib/dblink/dblink.sql.in
--- b/contrib/dblink/dblink.sql.in
***************
*** 15,20 **** RETURNS text
--- 15,31 ----
  AS 'MODULE_PATHNAME','dblink_connect'
  LANGUAGE C STRICT;

+ -- dblink_connect_s restricts users to predefined servers
+ CREATE OR REPLACE FUNCTION dblink_connect_s (text)
+ RETURNS text
+ AS 'MODULE_PATHNAME','dblink_connect_server'
+ LANGUAGE C STRICT;
+
+ CREATE OR REPLACE FUNCTION dblink_connect_s (text, text)
+ RETURNS text
+ AS 'MODULE_PATHNAME','dblink_connect_server'
+ LANGUAGE C STRICT;
+
  -- dblink_connect_u allows non-superusers to use
  -- non-password authenticated connections, but initially
  -- privileges are revoked from public
***************
*** 121,126 **** RETURNS setof record
--- 132,147 ----
  AS 'MODULE_PATHNAME','dblink_record'
  LANGUAGE C STRICT;

+ CREATE OR REPLACE FUNCTION dblink_s (text, text)
+ RETURNS setof record
+ AS 'MODULE_PATHNAME','dblink_record_server'
+ LANGUAGE C STRICT;
+
+ CREATE OR REPLACE FUNCTION dblink_s (text, text, boolean)
+ RETURNS setof record
+ AS 'MODULE_PATHNAME','dblink_record_server'
+ LANGUAGE C STRICT;
+
  CREATE OR REPLACE FUNCTION dblink_exec (text, text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_exec'
***************
*** 141,146 **** RETURNS text
--- 162,177 ----
  AS 'MODULE_PATHNAME','dblink_exec'
  LANGUAGE C STRICT;

+ CREATE OR REPLACE FUNCTION dblink_exec_s (text, text)
+ RETURNS text
+ AS 'MODULE_PATHNAME','dblink_exec_server'
+ LANGUAGE C STRICT;
+
+ CREATE OR REPLACE FUNCTION dblink_exec_s (text, text, boolean)
+ RETURNS text
+ AS 'MODULE_PATHNAME','dblink_exec_server'
+ LANGUAGE C STRICT;
+
  CREATE TYPE dblink_pkey_results AS (position int, colname text);

  CREATE OR REPLACE FUNCTION dblink_get_pkey (text)

Attachment

Re: SQL/MED compatible connection manager

From
Peter Eisentraut
Date:
Martin Pihlak wrote:
> Peter Eisentraut wrote:
>> If I read this right, SQL/MED requires option names to be unique for a
>> server.  To this needs to be rethought.
>>
>
> Attached is another revision of the connection manager, notable changes:

Attached is my current patch after surgery.  I have mainly worked on
making naming better and more consistent.

Problem: You have implemented foreign-data wrappers and foreign servers
as schema-qualified objects, but the standard has them outside schemas,
qualified only optionally by catalogs (a.k.a. databases).  I think that
should be fixed.

Attachment

Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Peter Eisentraut wrote:
> Attached is my current patch after surgery.  I have mainly worked on
> making naming better and more consistent.
> 
Thanks.

> Problem: You have implemented foreign-data wrappers and foreign servers
> as schema-qualified objects, but the standard has them outside schemas,
> qualified only optionally by catalogs (a.k.a. databases).  I think that
> should be fixed.

Darn. At least it is a lot easier to root out the schema support
than to add it ... Will look into it.

regards,
Martin







Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Peter Eisentraut wrote:
> Problem: You have implemented foreign-data wrappers and foreign servers
> as schema-qualified objects, but the standard has them outside schemas,
> qualified only optionally by catalogs (a.k.a. databases).  I think that
> should be fixed.

Attached. Removed schema support for foreign data wrapper and server,
updated documentation and regression tests.

Also added has_foreign_data_wrapper_privilege and has_server_privilege
functions. information_schema views use those to determine if user
has usage on the foreign data wrapper or server.

regards,
Martin




Attachment

Re: SQL/MED compatible connection manager

From
Peter Eisentraut
Date:
Now I have a question about the FDW C interface.  The way I understand 
it, an SQL/MED-enabled server and a FDW each have a specific API by 
which they communicate.  Supposedly, each database vendor should be able 
to ship a binary library for its FDW and each SQL/MED-enabled server 
should be able to load and use it.  (If you don't believe in binary 
compatibility, then I think there should at least be source-level 
interface compatibility.)

Now the way I read the FDWs you provide (default and pgsql), you are 
creating your own API for initialization and options validation that is 
not in the standard.  That would appear to contradict the idea of a 
standard interface.  I understand that option validation is useful, and 
I don't see anything about it in the standard, but should we break the 
API like that?  What are your designs about this?


Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Peter Eisentraut wrote:
> Now the way I read the FDWs you provide (default and pgsql), you are
> creating your own API for initialization and options validation that is
> not in the standard.  That would appear to contradict the idea of a
> standard interface.  I understand that option validation is useful, and
> I don't see anything about it in the standard, but should we break the
> API like that?  What are your designs about this?
> 

Hmm, in that perspective it would make sense to make the InitializeFdw
function optional (it was, before I got worried about library reloads).
If no InitializeFdw is present, connection lookup and option validation
are disabled. All of the standard defined FDW functions are fetched by
load_external_function. This way we could have the additional features
and still be able to load standard conforming FDW's.

Actually it would make sense to use _PG_init instead of InitializeFdw.
This way it'd be called automatically on library load, the parameter(s)
would be passed in globals though.

regards,
Martin



Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Peter Eisentraut wrote:
> Now the way I read the FDWs you provide (default and pgsql), you are
> creating your own API for initialization and options validation that is
> not in the standard.  That would appear to contradict the idea of a
> standard interface.

This is now fixed -- the option validation and connection lookup functions
have been made optional. InitializeFDW and _PG_fini have been dropped,
functions are looked up in GetForeignDataWrapperLibrary(). I decided not to
worry too much about the function pointers getting stale due to library
changes and reloads, as that requires some deliberate actions as a superuser.

I also added _pg prefixes to the non-standard functions so that these are not
confused with the standard FDW functions.

psql describe commands to \dew, \des and \deu as discussed in
http://archives.postgresql.org/pgsql-hackers/2008-12/msg00888.php

PS. Would it be more convenient to use the ~user area at git.postgresql.org
for review? I haven't requested a user account yet, but will do so if it
simplifies the review. The patches would still be posted to list as well.

regards,
Martin




Attachment

Re: SQL/MED compatible connection manager

From
"Jonah H. Harris"
Date:
On Fri, Dec 12, 2008 at 7:55 AM, Peter Eisentraut <peter_e@gmx.net> wrote:
> Now I have a question about the FDW C interface.  The way I understand it,
> an SQL/MED-enabled server and a FDW each have a specific API by which they
> communicate.  Supposedly, each database vendor should be able to ship a
> binary library for its FDW and each SQL/MED-enabled server should be able to
> load and use it.  (If you don't believe in binary compatibility, then I
> think there should at least be source-level interface compatibility.)

Yes, all FDWs should be similar to ODBC drivers in that they are
self-contained and interface with the database through a defined API.
What happens inside them should be irrelevant to PG.

-- 
Jonah H. Harris, Senior DBA
myYearbook.com


Re: SQL/MED compatible connection manager

From
Peter Eisentraut
Date:
On Monday 15 December 2008 22:30:19 Jonah H. Harris wrote:
> On Fri, Dec 12, 2008 at 7:55 AM, Peter Eisentraut <peter_e@gmx.net> wrote:
> > Now I have a question about the FDW C interface.  The way I understand
> > it, an SQL/MED-enabled server and a FDW each have a specific API by which
> > they communicate.  Supposedly, each database vendor should be able to
> > ship a binary library for its FDW and each SQL/MED-enabled server should
> > be able to load and use it.  (If you don't believe in binary
> > compatibility, then I think there should at least be source-level
> > interface compatibility.)
>
> Yes, all FDWs should be similar to ODBC drivers in that they are
> self-contained and interface with the database through a defined API.
> What happens inside them should be irrelevant to PG.

What we are currently trying to figure out is the best method to introduce 
extensions to the API.


Re: SQL/MED compatible connection manager

From
Peter Eisentraut
Date:
Martin Pihlak wrote:
> This is now fixed -- the option validation and connection lookup functions
> have been made optional. InitializeFDW and _PG_fini have been dropped,
> functions are looked up in GetForeignDataWrapperLibrary(). I decided not to
> worry too much about the function pointers getting stale due to library
> changes and reloads, as that requires some deliberate actions as a superuser.

I never understood that reload business complete anyway.  If you think
there are issues at run time, they should be documented somewhere.

> I also added _pg prefixes to the non-standard functions so that these are not
> confused with the standard FDW functions.

Yes, I think something like that should be OK.

> PS. Would it be more convenient to use the ~user area at git.postgresql.org
> for review? I haven't requested a user account yet, but will do so if it
> simplifies the review. The patches would still be posted to list as well.

Well, maybe a month ago. ;-)  We are getting pretty close to committing.


I am not satisfied with the custom SQL functions that you added:

| pg_get_foreign_data_wrapper_options(fdwid oid)
| pg_get_foreign_server_options(srvid oid)
| pg_get_user_mapping_options(umid oid)

I think these are basically just a way to parse apart {a=1,b=2} into a
table.  We could get more bang out of it, if we provided one function
that can do that parsing for all of fdwoptions, srvoptions, umoptions,
reloptions, datconfig, useconfig, proconfig.  (reloptions and *config
use different parsers internally, but maybe that is not so important for
this problem.)


The other thing that I am not settled on is the default FDW (I renamed
it to dummy).  In principle, this thing should do nothing, so the source
file ought to empty.  Well, _pg_validateOptionList *is* empty, but
_pg_GetConnectionInfo has an excuse implementation that makes me think
that the pg_get_remote_connection_info() function has a too specific
mission to be a general function.  If we added, say, an XML-file FDW,
what sense would pg_get_remote_connection_info() make?


For the record, my current patch, which merges all your latest changes,
is attached.

I could offer, if it turns out to be possible, that I cut these
contentious parts out of the patch and commit the rest as soon as
possible, because I am growing weary of moving this big patch around. :-)

Attachment

Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Peter Eisentraut wrote:
>> worry too much about the function pointers getting stale due to library
>> changes and reloads, as that requires some deliberate actions as a
>> superuser.
> 
> I never understood that reload business complete anyway.  If you think
> there are issues at run time, they should be documented somewhere.
> 

Lets say a backend has the library loaded and the FDW function pointers
already initialized. Now the FDW library file is upgraded, and the user
issues a LOAD command to reload the library. The library is reloaded, but
the function pointers never get updated. Attempt to use the FDW functions
most likely crashes the server.

The options are:
- always look up functions immediately before use (performance penalty)
- use _PG_fini callback to register FDW unloads (needs cooperating library)
- document that reloading is not supported (ie. this is a feature)
- just ignore it, as there are probably a dozen more ways a superuser can crash the server.

> I am not satisfied with the custom SQL functions that you added:
> 
> | pg_get_foreign_data_wrapper_options(fdwid oid)
> | pg_get_foreign_server_options(srvid oid)
> | pg_get_user_mapping_options(umid oid)
> 
> I think these are basically just a way to parse apart {a=1,b=2} into a
> table.

Hmm, I just realized that there are only OID variants of those -- indeed
those are not very useful. If names were used instead, the functions would
be a lot more useful. Especially so, if FDW doesn't provide connection
lookup.

> The other thing that I am not settled on is the default FDW (I renamed
> it to dummy).  In principle, this thing should do nothing, so the source
> file ought to empty.  Well, _pg_validateOptionList *is* empty, but
> _pg_GetConnectionInfo has an excuse implementation that makes me think
> that the pg_get_remote_connection_info() function has a too specific
> mission to be a general function.  If we added, say, an XML-file FDW,
> what sense would pg_get_remote_connection_info() make?
> 

It'd make more sense if we changed the name to pg_get_datasource ;)

We could make the pg_get_remote_connection_info Postgres specific -- in
this case it would be changed to return just the connect string text. NULL
for the other wrappers -- for these use the pg_get*options to construct
the connect strings. Comments?

One more thing just occured to me -- the dummy and postgresql wrappers are
currently installed by initdb. The majority of installations will probably
never use them. So I think it would make sense to ship with no predefined
FDW-s.

regards,
Martin





Re: SQL/MED compatible connection manager

From
Peter Eisentraut
Date:
I have committed this without the functions.  I have some thoughts on 
what to do about that, but right now I'm exhausted. ;-)




Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Peter Eisentraut wrote:
> I have committed this without the functions.  I have some thoughts on
> what to do about that, but right now I'm exhausted. ;-)
> 
Great news :) Thanks a lot for your support and contributions!

regards,
Martin



Re: SQL/MED compatible connection manager

From
Peter Eisentraut
Date:
On Wednesday 17 December 2008 17:17:26 Martin Pihlak wrote:
> It'd make more sense if we changed the name to pg_get_datasource ;)
>
> We could make the pg_get_remote_connection_info Postgres specific -- in
> this case it would be changed to return just the connect string text. NULL
> for the other wrappers -- for these use the pg_get*options to construct
> the connect strings. Comments?

Well, what this function essentially does is a text transformation of the 
options, something like this:

peter=# SELECT array_to_string(fdwoptions || srvoptions || umoptions, ' ') 
FROM pg_foreign_data_wrapper fdw, pg_foreign_server srv, pg_user_mappings um 
WHERE fdw.oid = srv.srvfdw AND srv.oid = um.srvid;                  array_to_string
-----------------------------------------------------host=localhost port=5432 user=peter password=seKret
(1 row)

(You can enhance this with quoting etc., but that's the essence.)

So, we could add a function whose job it is to convert all options to a 
PostgreSQL connection string.  I wouldn't worry about dealing with other 
wrappers specifically.  They could still use the function, but the result 
would not make much sense.

I would call it something like

pg_postgresql_fdw_options_string(server, user) returns text

Not sure if a C implementation based on your old function or an SQL 
implementation is better.


Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Peter Eisentraut wrote:
> Well, what this function essentially does is a text transformation of the 
> options, something like this:
> 
> peter=# SELECT array_to_string(fdwoptions || srvoptions || umoptions, ' ') 
> FROM pg_foreign_data_wrapper fdw, pg_foreign_server srv, pg_user_mappings um 
> WHERE fdw.oid = srv.srvfdw AND srv.oid = um.srvid;
>                    array_to_string
> -----------------------------------------------------
>  host=localhost port=5432 user=peter password=seKret
> (1 row)
> 
> (You can enhance this with quoting etc., but that's the essence.)

Essentially yes. Additional things include USAGE check on the server and user
mapping lookup (use public if no explicit mapping is specified). Without those
I'm not really sure this deserves a separate function at all. The main goal
is to provide standard semantics for the connection lookup, so that dblink,
plproxy, pl rpc etc. would not have to reinvent it.

> So, we could add a function whose job it is to convert all options to a 
> PostgreSQL connection string.  I wouldn't worry about dealing with other 
> wrappers specifically.  They could still use the function, but the result 
> would not make much sense.
> 
This works for me. I'd implement this as a C function so it is directly
callable from other C modules.

Another option is to implement it as a SRF, similar to what was initially in the
dummy wrapper. Just return all of the options for fdw, server and user mapping.
This is probably worth doing if there are any users for this. So far I haven't
noticed any enthusiasm, so it might be better to start with just the connection
string.

> I would call it something like
> 
> pg_postgresql_fdw_options_string(server, user) returns text

Hmm, it is probably a good idea to avoid the fdw abbreviation -- the term
"foreign data wrapper" is already confusing enough. My suggestion:

pg_foreign_server_conninfo(server)
pg_foreign_server_conninfo(server,user)

If there are no objections, I'll whack those functions out, and bring the dblink
patch up to date.

regards,
Martin



Re: SQL/MED compatible connection manager

From
David Fetter
Date:
On Thu, Jan 01, 2009 at 11:10:38PM +0200, Martin Pihlak wrote:
> Peter Eisentraut wrote:
> > Well, what this function essentially does is a text transformation of the 
> > options, something like this:
> > 
> > peter=# SELECT array_to_string(fdwoptions || srvoptions || umoptions, ' ') 
> > FROM pg_foreign_data_wrapper fdw, pg_foreign_server srv, pg_user_mappings um 
> > WHERE fdw.oid = srv.srvfdw AND srv.oid = um.srvid;
> >                    array_to_string
> > -----------------------------------------------------
> >  host=localhost port=5432 user=peter password=seKret
> > (1 row)
> > 
> > (You can enhance this with quoting etc., but that's the essence.)
> 
> Essentially yes. Additional things include USAGE check on the server and user
> mapping lookup (use public if no explicit mapping is specified). Without those
> I'm not really sure this deserves a separate function at all. The main goal
> is to provide standard semantics for the connection lookup, so that dblink,
> plproxy, pl rpc etc. would not have to reinvent it.
> 
> > So, we could add a function whose job it is to convert all options to a 
> > PostgreSQL connection string.  I wouldn't worry about dealing with other 
> > wrappers specifically.  They could still use the function, but the result 
> > would not make much sense.
> > 
> This works for me. I'd implement this as a C function so it is
> directly callable from other C modules.
> 
> Another option is to implement it as a SRF, similar to what was
> initially in the dummy wrapper. Just return all of the options for
> fdw, server and user mapping.  This is probably worth doing if there
> are any users for this. So far I haven't noticed any enthusiasm, so
> it might be better to start with just the connection string.

The connection string could be pretty different if it's not a
PostgreSQL database, so +1 on the SRF option :)

Cheers,
David.
-- 
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778  AIM: dfetter666  Yahoo!: dfetter
Skype: davidfetter      XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate


Re: SQL/MED compatible connection manager

From
Peter Eisentraut
Date:
Martin Pihlak wrote:
>> I would call it something like
>>
>> pg_postgresql_fdw_options_string(server, user) returns text
> 
> Hmm, it is probably a good idea to avoid the fdw abbreviation -- the term
> "foreign data wrapper" is already confusing enough. My suggestion:
> 
> pg_foreign_server_conninfo(server)
> pg_foreign_server_conninfo(server,user)
> 
> If there are no objections, I'll whack those functions out, and bring the dblink
> patch up to date.

Sure, propose some code.  (Note that you can use parameter default 
values now.)


Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Peter Eisentraut wrote:
> Martin Pihlak wrote:
>>> I would call it something like
>>>
>>> pg_postgresql_fdw_options_string(server, user) returns text
>>
>> Hmm, it is probably a good idea to avoid the fdw abbreviation -- the term
>> "foreign data wrapper" is already confusing enough. My suggestion:
>>
>> pg_foreign_server_conninfo(server)
>> pg_foreign_server_conninfo(server,user)
>>
>> If there are no objections, I'll whack those functions out, and bring
>> the dblink
>> patch up to date.
>
> Sure, propose some code.  (Note that you can use parameter default
> values now.)
>

Proposal attached. This adds two C functions:

List *GetForeignConnectionOptions(Oid serverid, Oid userid);
char *GetForeignConnectionString(Oid serverid, Oid userid);

One for obtaining all of the connection related options as a list, and
another for transforming these options into a libpq conninfo string.
The latter should be useful for dblink (although the userid and serverid
need to be obtained first).

On top of those there are two SQL accessible functions:

pg_foreign_connection_options(server name, user name = current_user,
    OUT option_class text, OUT option_name text, OUT option_value text);

pg_foreign_connection_string(server name, user  name = current_user);

These should initially be restricted from ordinary users -- grant explicitly
if the user should see the connect strings. Otherwise use from security definer
functions. The pg_foreign_connection_options() exposes all of the connection
options and can be used by clients such as DBI link to construct the connect
string or equivalent. pg_foreign_connection_string() can be used for instance
by plpythonu or plperlu functions to connect to remote postgres database.

Example:

select * from pg_foreign_connection_options('foo');

 option_class | option_name | option_value
--------------+-------------+--------------
 server       | host        | localhost
 server       | port        | 54321
 server       | dbname      | foo
 user mapping | user        | bob
 user mapping | password    | secret
(5 rows)

select * from pg_foreign_connection_string('foo');

                      pg_foreign_connection_string
-------------------------------------------------------------------------
 host='localhost' port='54321' dbname='foo' user='bob' password='secret'
(1 row)

Will add regression and tests if this is acceptable.

PS. I'm not sure if I nailed the "proargdefaults" syntax correctly in pg_proc.h,
for now I just copied it out from a sample function with similar arguments.

regards,
Martin

*** a/src/backend/foreign/foreign.c
--- b/src/backend/foreign/foreign.c
***************
*** 31,36 ****
--- 31,38 ----


  extern Datum pg_options_to_table(PG_FUNCTION_ARGS);
+ extern Datum pg_foreign_connection_string(PG_FUNCTION_ARGS);
+ extern Datum pg_foreign_connection_options(PG_FUNCTION_ARGS);


  /* list of currently loaded foreign-data wrapper interfaces */
***************
*** 321,338 **** GetUserMapping(Oid userid, Oid serverid)
  }


  /*
!  * 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;

--- 323,447 ----
  }


+ /*
+  * Helper for appending a ForeignConnectionOption node to a list.
+  */
+ static List *
+ append_option_list(List *options, GenericOptionFlags type, DefElem *def)
+ {
+     ForeignConnectionOption       *opt = makeNode(ForeignConnectionOption);
+
+     opt->opttype = type;
+     opt->option = def;
+     return lappend(options, opt);
+ }
+
+
+ /*
+  * GetForeignConnectionOptions - look up the options for foreign connection.
+  *
+  * Foreign connection is defined by the foreign data wrapper, server and
+  * user mapping triple. The options are simply merged together into a list
+  * of ForeignConnectionOption nodes.
+  *
+  * The role specified by userid must have a user mapping and USAGE privilege
+  * on the server.
+  */
+ List *
+ GetForeignConnectionOptions(Oid serverid, Oid userid)
+ {
+     ForeignServer       *server;
+     UserMapping           *um;
+     ForeignDataWrapper *fdw;
+     List               *result = NIL;
+     ListCell           *cell;
+     AclResult            aclresult;
+
+     server = GetForeignServer(serverid);
+     um = GetUserMapping(userid, serverid);
+     fdw    = GetForeignDataWrapper(server->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, server->servername);
+
+     /* Seems OK, prepare a list of all the options */
+     foreach (cell, fdw->options)
+         result = append_option_list(result, FdwOpt, lfirst(cell));
+     foreach (cell, server->options)
+         result = append_option_list(result, ServerOpt, lfirst(cell));
+     foreach (cell, um->options)
+         result = append_option_list(result, UserMappingOpt, lfirst(cell));
+
+     return result;
+ }
+
+
+ /*
+  * GetForeignConnectionString - return a libpq conninfo string for the
+  * foreign connection.
+  *
+  * Currently this just means transforming all of the fdw, server and user
+  * mapping options into a single string. No attempt is made to check if the
+  * resulting conninfo string is valid.
+  */
+ char *
+ GetForeignConnectionString(Oid serverid, Oid userid)
+ {
+     StringInfo        stringptr = makeStringInfo();
+     ListCell       *cell;
+
+     foreach (cell, GetForeignConnectionOptions(serverid, userid))
+     {
+         ForeignConnectionOption       *opt = lfirst(cell);
+
+         appendStringInfo(stringptr, "%s%s='%s'",
+                          (stringptr->len > 0) ? " " : "",
+                          opt->option->defname,
+                          strVal(opt->option->arg));
+     }
+
+     return stringptr->data;
+ }
+
+
+ /*
+  * Helper function for obtaining description of the option type.
+  */
+ static const char *
+ optclass_name(GenericOptionFlags opttype)
+ {
+     switch (opttype)
+     {
+         case FdwOpt:
+             return "foreign data wrapper";
+         case ServerOpt:
+             return "server";
+         case UserMappingOpt:
+             return "user mapping";
+         default:
+             return "unknown";
+     }
+
+     /* not reached */
+     return NULL;
+ }
+
  /*
!  * list_to_tuplestore - Helper function to convert a list to
   * tuplestore usable in SRF.
+  *
+  * All the list elements are assumed to nodes of type "type".
   */
  static void
! list_to_tuplestore(ReturnSetInfo *rsinfo, List *options, NodeTag type)
  {
      ListCell   *cell;
      TupleDesc    tupdesc;
      Tuplestorestate *tupstore;
!     Datum        values[3];
!     bool        nulls[3] = { 0 };
      MemoryContext per_query_ctx;
      MemoryContext oldcontext;

***************
*** 360,369 **** deflist_to_tuplestore(ReturnSetInfo *rsinfo, List *options)

      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);
      }

--- 469,502 ----

      foreach (cell, options)
      {
!         NodeTag        nodeType = ((Node *)lfirst(cell))->type;
!
!         Assert(nodeType == type);
!
!         if (nodeType == T_DefElem)
!         {
!             /* Simple key/value list */
!             DefElem *def = lfirst(cell);
!
!             values[0] = CStringGetTextDatum(def->defname);
!             values[1] = CStringGetTextDatum(strVal(def->arg));
!         }
!         else if (nodeType == T_ForeignConnectionOption)
!         {
!             /* Foreign connection option list - class, key, value */
!             ForeignConnectionOption *opt = lfirst(cell);
!
!             values[0] = CStringGetTextDatum(optclass_name(opt->opttype));
!             values[1] = CStringGetTextDatum(opt->option->defname);
!             values[2] = CStringGetTextDatum(strVal(opt->option->arg));
!         }
!         else
!         {
!             ereport(ERROR,
!                     (errcode(ERRCODE_INTERNAL_ERROR),
!                      errmsg("unrecognized node type %d in list_to_tuplestore", type)));
!         }

          tuplestore_putvalues(tupstore, tupdesc, values, nulls);
      }

***************
*** 383,389 **** pg_options_to_table(PG_FUNCTION_ARGS)
  {
      Datum array = PG_GETARG_DATUM(0);

!     deflist_to_tuplestore((ReturnSetInfo *) fcinfo->resultinfo, untransformRelOptions(array));

      return (Datum) 0;
  }
--- 516,562 ----
  {
      Datum array = PG_GETARG_DATUM(0);

!     list_to_tuplestore((ReturnSetInfo *) fcinfo->resultinfo, untransformRelOptions(array), T_DefElem);
!
!     return (Datum) 0;
! }
!
!
! /*
!  * Return the libpq connection string for specified server and user.
!  */
! Datum
! pg_foreign_connection_string(PG_FUNCTION_ARGS)
! {
!     Name    servername = PG_GETARG_NAME(0);
!     Name    username = PG_GETARG_NAME(1);
!     Oid        serverid;
!     Oid        userid;
!
!     userid = get_roleid_checked(NameStr(*username));
!     serverid = GetForeignServerOidByName(NameStr(*servername), false);
!
!     return CStringGetTextDatum(GetForeignConnectionString(serverid, userid));
! }
!
!
! /*
!  * Return the connection options for specified server and user.
!  */
! Datum
! pg_foreign_connection_options(PG_FUNCTION_ARGS)
! {
!     Name    servername = PG_GETARG_NAME(0);
!     Name    username = PG_GETARG_NAME(1);
!     Oid        serverid;
!     Oid        userid;
!     List   *options;
!
!     userid = get_roleid_checked(NameStr(*username));
!     serverid = GetForeignServerOidByName(NameStr(*servername), false);
!     options = GetForeignConnectionOptions(serverid, userid);
!
!     list_to_tuplestore((ReturnSetInfo *) fcinfo->resultinfo, options, T_ForeignConnectionOption);

      return (Datum) 0;
  }
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 2319,2324 **** DESCR("list of SQL keywords");
--- 2319,2330 ----
  DATA(insert OID = 2289 (  pg_options_to_table        PGNSP PGUID 12 1 3 0 f 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");

+ DATA(insert OID = 2316 (  pg_foreign_connection_string        PGNSP PGUID 12 1 0 0 f f f t f s 2 1 25 "19 19" _null_
_null_"{servername,username}" "({FUNCEXPR :funcid 745 :funcresulttype 19 :funcretset false :funcformat 0 :args <>
:location33})" pg_foreign_connection_string _null_ _null_ _null_ )); 
+ DESCR("returns the libpq conninfo string for the server and username");
+
+ DATA(insert OID = 2319 (  pg_foreign_connection_options        PGNSP PGUID 12 1 5 0 f f f t t s 2 1 2249 "19 19"
"{19,19,25,25,25}""{i,i,o,o,o}" "{servername,username,option_class,option_name,option_value}" "({FUNCEXPR :funcid 745
:funcresulttype19 :funcretset false :funcformat 0 :args <> :location 33})" pg_foreign_connection_options _null_ _null_
_null_)); 
+ DESCR("returns the foreign connection options for the server and username");
+
  DATA(insert OID = 1619 (  pg_typeof                PGNSP PGUID 12 1 0 0 f f f f f s 1 0 2206 "2276" _null_ _null_
_null__null_  pg_typeof _null_ _null_ _null_ )); 
  DESCR("returns the type of the argument");

*** a/src/include/foreign/foreign.h
--- b/src/include/foreign/foreign.h
***************
*** 65,70 **** typedef struct UserMapping
--- 65,78 ----
  } UserMapping;


+ typedef struct ForeignConnectionOption
+ {
+     NodeTag                type;        /* Node type */
+     GenericOptionFlags    opttype;    /* Option type, used as integer value, not flags */
+     DefElem               *option;        /* Option name and value */
+ } ForeignConnectionOption;
+
+
  /*
   * Foreign-data wrapper library function types.
   */
***************
*** 93,98 **** extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
--- 101,108 ----
                                                         bool missing_ok);
  extern Oid GetForeignDataWrapperOidByName(const char *name, bool missing_ok);
  extern ForeignDataWrapperLibrary *GetForeignDataWrapperLibrary(const char *libname);
+ extern List *GetForeignConnectionOptions(Oid serverid, Oid userid);
+ extern char *GetForeignConnectionString(Oid serverid, Oid userid);

  /* Foreign data wrapper interface functions */
  extern void _pg_validateOptionList(ForeignDataWrapper *fdw,
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 390,396 **** typedef enum NodeTag
      T_TriggerData = 950,        /* in commands/trigger.h */
      T_ReturnSetInfo,            /* in nodes/execnodes.h */
      T_WindowObjectData,            /* private in nodeWindowAgg.c */
!     T_TIDBitmap                    /* in nodes/tidbitmap.h */
  } NodeTag;

  /*
--- 390,397 ----
      T_TriggerData = 950,        /* in commands/trigger.h */
      T_ReturnSetInfo,            /* in nodes/execnodes.h */
      T_WindowObjectData,            /* private in nodeWindowAgg.c */
!     T_TIDBitmap,                /* in nodes/tidbitmap.h */
!     T_ForeignConnectionOption     /* private in foreign/foreign.h */
  } NodeTag;

  /*

Re: SQL/MED compatible connection manager

From
Peter Eisentraut
Date:
Martin Pihlak wrote:
> Proposal attached. This adds two C functions:
> 
> List *GetForeignConnectionOptions(Oid serverid, Oid userid);
> char *GetForeignConnectionString(Oid serverid, Oid userid);
> 
> One for obtaining all of the connection related options as a list, and
> another for transforming these options into a libpq conninfo string.
> The latter should be useful for dblink (although the userid and serverid
> need to be obtained first).
> 
> On top of those there are two SQL accessible functions:
> 
> pg_foreign_connection_options(server name, user name = current_user,
>     OUT option_class text, OUT option_name text, OUT option_value text);
> 
> pg_foreign_connection_string(server name, user  name = current_user);
> 
> These should initially be restricted from ordinary users -- grant explicitly
> if the user should see the connect strings.

Back to this one ...

I have been thinking about this for a great while now.  I am not yet 
comfortable with how we manage the access rights here.  We have 
restricted access to the user mappings catalog to hide passwords, but it 
is not entirely clear why a password must be stored in a user mapping. 
It could also be stored with a server, if we only want to use one global 
connection for everybody.

I think the proper way to handle it might be to introduce a new 
privilege type -- call it SELECT if you like -- that determines 
specifically whether you can *see* the options of a foreign-data 
wrapper, foreign server, or user mapping, respectively.  As opposed to 
USAGE, which means you can use the object for connecting (in the 
future).  This might have other uses: The owner of a server might want 
to hide the host name, but still let you connect.

Comments?



Re: SQL/MED compatible connection manager

From
Tom Lane
Date:
Peter Eisentraut <peter_e@gmx.net> writes:
> I think the proper way to handle it might be to introduce a new 
> privilege type -- call it SELECT if you like -- that determines 
> specifically whether you can *see* the options of a foreign-data 
> wrapper, foreign server, or user mapping, respectively.  As opposed to 
> USAGE, which means you can use the object for connecting (in the 
> future).  This might have other uses: The owner of a server might want 
> to hide the host name, but still let you connect.

How would you implement/enforce that, in the absence of row-level
security on the catalogs involved?
        regards, tom lane


Re: SQL/MED compatible connection manager

From
David Fetter
Date:
On Wed, Mar 04, 2009 at 03:26:36PM +0200, Peter Eisentraut wrote:
> Martin Pihlak wrote:
>> Proposal attached. This adds two C functions:
>>
>> List *GetForeignConnectionOptions(Oid serverid, Oid userid);
>> char *GetForeignConnectionString(Oid serverid, Oid userid);
>>
>> One for obtaining all of the connection related options as a list,
>> and another for transforming these options into a libpq conninfo
>> string.  The latter should be useful for dblink (although the
>> userid and serverid need to be obtained first).
>>
>> On top of those there are two SQL accessible functions:
>>
>> pg_foreign_connection_options(server name, user name =
>> current_user, OUT option_class text, OUT option_name text, OUT
>> option_value text);
>>
>> pg_foreign_connection_string(server name, user  name =
>> current_user);
>>
>> These should initially be restricted from ordinary users -- grant
>> explicitly if the user should see the connect strings.
>
> Back to this one ...
>
> I have been thinking about this for a great while now.  I am not yet
> comfortable with how we manage the access rights here.  We have
> restricted access to the user mappings catalog to hide passwords,
> but it  is not entirely clear why a password must be stored in a
> user mapping.  It could also be stored with a server, if we only
> want to use one global  connection for everybody.
>
> I think the proper way to handle it might be to introduce a new
> privilege type -- call it SELECT if you like -- that determines
> specifically whether you can *see* the options of a foreign-data
> wrapper, foreign server, or user mapping, respectively.  As opposed
> to  USAGE, which means you can use the object for connecting (in the
> future).  This might have other uses: The owner of a server might
> want  to hide the host name, but still let you connect.
>
> Comments?

This could have a more general usage, too.  Does SQL:2008 have
anything to say about such a capability, or is it already in the
column-level privileges, or...?

Cheers,
David.
-- 
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778  AIM: dfetter666  Yahoo!: dfetter
Skype: davidfetter      XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate


Re: SQL/MED compatible connection manager

From
Martin Pihlak
Date:
Peter Eisentraut wrote:
> I have been thinking about this for a great while now.  I am not yet
> comfortable with how we manage the access rights here.  We have
> restricted access to the user mappings catalog to hide passwords, but it
> is not entirely clear why a password must be stored in a user mapping.
> It could also be stored with a server, if we only want to use one global
> connection for everybody.
> 

Hmm, in this case one would probably create a PUBLIC user mapping and
store the password there. But indeed, there could be other aspects
of the server that need to be kept secret.

> I think the proper way to handle it might be to introduce a new
> privilege type -- call it SELECT if you like -- that determines
> specifically whether you can *see* the options of a foreign-data
> wrapper, foreign server, or user mapping, respectively.

How about providing an optional masking function for the foreign data
wrapper. The function would accept the generic options array and remove/mask
any undesired options. Ordinary users would access the catalogs by views, and
only see the filtered or masked options. The owner and superuser would
still have to get the full options though.

Just an idea.

regards,
Martin