mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-11-27 07:21:09 +08:00
Create an improved FDW option validator function for contrib/dblink.
dblink now has its own validator function dblink_fdw_validator(), which is better than the core function postgresql_fdw_validator() because it gets the list of legal options from libpq instead of having a hard-wired list. Make the dblink extension module provide a standard foreign data wrapper dblink_fdw that encapsulates use of this validator, and recommend use of that wrapper instead of making up wrappers on the fly. Unfortunately, because ad-hoc wrappers *were* recommended practice previously, it's not clear when we can get rid of postgresql_fdw_validator without causing upgrade problems. But this is a step in the right direction. Shigeru Hanada, reviewed by KaiGai Kohei
This commit is contained in:
parent
392b2e5010
commit
8255566f9d
@ -7,7 +7,7 @@ SHLIB_LINK = $(libpq)
|
||||
SHLIB_PREREQS = submake-libpq
|
||||
|
||||
EXTENSION = dblink
|
||||
DATA = dblink--1.0.sql dblink--unpackaged--1.0.sql
|
||||
DATA = dblink--1.1.sql dblink--1.0--1.1.sql dblink--unpackaged--1.0.sql
|
||||
|
||||
REGRESS = dblink
|
||||
|
||||
|
14
contrib/dblink/dblink--1.0--1.1.sql
Normal file
14
contrib/dblink/dblink--1.0--1.1.sql
Normal file
@ -0,0 +1,14 @@
|
||||
/* contrib/dblink/dblink--1.0--1.1.sql */
|
||||
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "ALTER EXTENSION dblink UPDATE TO '1.1'" to load this file. \quit
|
||||
|
||||
CREATE FUNCTION dblink_fdw_validator(
|
||||
options text[],
|
||||
catalog oid
|
||||
)
|
||||
RETURNS void
|
||||
AS 'MODULE_PATHNAME', 'dblink_fdw_validator'
|
||||
LANGUAGE C STRICT;
|
||||
|
||||
CREATE FOREIGN DATA WRAPPER dblink_fdw VALIDATOR dblink_fdw_validator;
|
@ -1,4 +1,4 @@
|
||||
/* contrib/dblink/dblink--1.0.sql */
|
||||
/* contrib/dblink/dblink--1.1.sql */
|
||||
|
||||
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "CREATE EXTENSION dblink" to load this file. \quit
|
||||
@ -221,3 +221,15 @@ CREATE FUNCTION dblink_get_notify(
|
||||
RETURNS setof record
|
||||
AS 'MODULE_PATHNAME', 'dblink_get_notify'
|
||||
LANGUAGE C STRICT;
|
||||
|
||||
/* New stuff in 1.1 begins here */
|
||||
|
||||
CREATE FUNCTION dblink_fdw_validator(
|
||||
options text[],
|
||||
catalog oid
|
||||
)
|
||||
RETURNS void
|
||||
AS 'MODULE_PATHNAME', 'dblink_fdw_validator'
|
||||
LANGUAGE C STRICT;
|
||||
|
||||
CREATE FOREIGN DATA WRAPPER dblink_fdw VALIDATOR dblink_fdw_validator;
|
@ -37,9 +37,12 @@
|
||||
#include "libpq-fe.h"
|
||||
|
||||
#include "access/htup_details.h"
|
||||
#include "access/reloptions.h"
|
||||
#include "catalog/indexing.h"
|
||||
#include "catalog/namespace.h"
|
||||
#include "catalog/pg_foreign_server.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "catalog/pg_user_mapping.h"
|
||||
#include "executor/spi.h"
|
||||
#include "foreign/foreign.h"
|
||||
#include "funcapi.h"
|
||||
@ -113,6 +116,8 @@ static char *escape_param_str(const char *from);
|
||||
static void validate_pkattnums(Relation rel,
|
||||
int2vector *pkattnums_arg, int32 pknumatts_arg,
|
||||
int **pkattnums, int *pknumatts);
|
||||
static bool is_valid_dblink_option(const PQconninfoOption *options,
|
||||
const char *option, Oid context);
|
||||
|
||||
/* Global */
|
||||
static remoteConn *pconn = NULL;
|
||||
@ -1912,6 +1917,75 @@ dblink_get_notify(PG_FUNCTION_ARGS)
|
||||
return (Datum) 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate the options given to a dblink foreign server or user mapping.
|
||||
* Raise an error if any option is invalid.
|
||||
*
|
||||
* We just check the names of options here, so semantic errors in options,
|
||||
* such as invalid numeric format, will be detected at the attempt to connect.
|
||||
*/
|
||||
PG_FUNCTION_INFO_V1(dblink_fdw_validator);
|
||||
Datum
|
||||
dblink_fdw_validator(PG_FUNCTION_ARGS)
|
||||
{
|
||||
List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
|
||||
Oid context = PG_GETARG_OID(1);
|
||||
ListCell *cell;
|
||||
|
||||
static const PQconninfoOption *options = NULL;
|
||||
|
||||
/*
|
||||
* Get list of valid libpq options.
|
||||
*
|
||||
* To avoid unnecessary work, we get the list once and use it throughout
|
||||
* the lifetime of this backend process. We don't need to care about
|
||||
* memory context issues, because PQconndefaults allocates with malloc.
|
||||
*/
|
||||
if (!options)
|
||||
{
|
||||
options = PQconndefaults();
|
||||
if (!options) /* assume reason for failure is OOM */
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FDW_OUT_OF_MEMORY),
|
||||
errmsg("out of memory"),
|
||||
errdetail("could not get libpq's default connection options")));
|
||||
}
|
||||
|
||||
/* Validate each supplied option. */
|
||||
foreach(cell, options_list)
|
||||
{
|
||||
DefElem *def = (DefElem *) lfirst(cell);
|
||||
|
||||
if (!is_valid_dblink_option(options, def->defname, context))
|
||||
{
|
||||
/*
|
||||
* Unknown option, or invalid option for the context specified,
|
||||
* so complain about it. Provide a hint with list of valid
|
||||
* options for the context.
|
||||
*/
|
||||
StringInfoData buf;
|
||||
const PQconninfoOption *opt;
|
||||
|
||||
initStringInfo(&buf);
|
||||
for (opt = options; opt->keyword; opt++)
|
||||
{
|
||||
if (is_valid_dblink_option(options, opt->keyword, context))
|
||||
appendStringInfo(&buf, "%s%s",
|
||||
(buf.len > 0) ? ", " : "",
|
||||
opt->keyword);
|
||||
}
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FDW_OPTION_NAME_NOT_FOUND),
|
||||
errmsg("invalid option \"%s\"", def->defname),
|
||||
errhint("Valid options in this context are: %s",
|
||||
buf.data)));
|
||||
}
|
||||
}
|
||||
|
||||
PG_RETURN_VOID();
|
||||
}
|
||||
|
||||
|
||||
/*************************************************************
|
||||
* internal functions
|
||||
*/
|
||||
@ -2768,3 +2842,59 @@ validate_pkattnums(Relation rel,
|
||||
errmsg("invalid attribute number %d", pkattnum)));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the specified connection option is valid.
|
||||
*
|
||||
* We basically allow whatever libpq thinks is an option, with these
|
||||
* restrictions:
|
||||
* debug options: disallowed
|
||||
* "client_encoding": disallowed
|
||||
* "user": valid only in USER MAPPING options
|
||||
* secure options (eg password): valid only in USER MAPPING options
|
||||
* others: valid only in FOREIGN SERVER options
|
||||
*
|
||||
* We disallow client_encoding because it would be overridden anyway via
|
||||
* PQclientEncoding; allowing it to be specified would merely promote
|
||||
* confusion.
|
||||
*/
|
||||
static bool
|
||||
is_valid_dblink_option(const PQconninfoOption *options, const char *option,
|
||||
Oid context)
|
||||
{
|
||||
const PQconninfoOption *opt;
|
||||
|
||||
/* Look up the option in libpq result */
|
||||
for (opt = options; opt->keyword; opt++)
|
||||
{
|
||||
if (strcmp(opt->keyword, option) == 0)
|
||||
break;
|
||||
}
|
||||
if (opt->keyword == NULL)
|
||||
return false;
|
||||
|
||||
/* Disallow debug options (particularly "replication") */
|
||||
if (strchr(opt->dispchar, 'D'))
|
||||
return false;
|
||||
|
||||
/* Disallow "client_encoding" */
|
||||
if (strcmp(opt->keyword, "client_encoding") == 0)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* If the option is "user" or marked secure, it should be specified only
|
||||
* in USER MAPPING. Others should be specified only in SERVER.
|
||||
*/
|
||||
if (strcmp(opt->keyword, "user") == 0 || strchr(opt->dispchar, '*'))
|
||||
{
|
||||
if (context != UserMappingRelationId)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (context != ForeignServerRelationId)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
# dblink extension
|
||||
comment = 'connect to other PostgreSQL databases from within a database'
|
||||
default_version = '1.0'
|
||||
default_version = '1.1'
|
||||
module_pathname = '$libdir/dblink'
|
||||
relocatable = true
|
||||
|
@ -58,5 +58,6 @@ extern Datum dblink_build_sql_delete(PG_FUNCTION_ARGS);
|
||||
extern Datum dblink_build_sql_update(PG_FUNCTION_ARGS);
|
||||
extern Datum dblink_current_query(PG_FUNCTION_ARGS);
|
||||
extern Datum dblink_get_notify(PG_FUNCTION_ARGS);
|
||||
extern Datum dblink_fdw_validator(PG_FUNCTION_ARGS);
|
||||
|
||||
#endif /* DBLINK_H */
|
||||
|
@ -783,8 +783,20 @@ 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 SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw
|
||||
OPTIONS (invalid 'val'); -- fail, invalid option
|
||||
ERROR: invalid option "invalid"
|
||||
HINT: Valid options in this context are: service, connect_timeout, dbname, host, hostaddr, port, application_name, fallback_application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer
|
||||
CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw
|
||||
OPTIONS (password 'val'); -- fail, can't specify password here
|
||||
ERROR: invalid option "password"
|
||||
HINT: Valid options in this context are: service, connect_timeout, dbname, host, hostaddr, port, application_name, fallback_application_name, keepalives, keepalives_idle, keepalives_interval, keepalives_count, sslmode, sslcompression, sslcert, sslkey, sslrootcert, sslcrl, requirepeer
|
||||
CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw
|
||||
OPTIONS (dbname 'contrib_regression');
|
||||
CREATE USER MAPPING FOR public SERVER fdtest
|
||||
OPTIONS (server 'localhost'); -- fail, can't specify server here
|
||||
ERROR: invalid option "server"
|
||||
HINT: Valid options in this context are: user, password
|
||||
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;
|
||||
@ -823,7 +835,6 @@ REVOKE EXECUTE ON FUNCTION dblink_connect_u(text, text) FROM dblink_regression_t
|
||||
DROP USER dblink_regression_test;
|
||||
DROP USER MAPPING FOR public SERVER fdtest;
|
||||
DROP SERVER fdtest;
|
||||
DROP FOREIGN DATA WRAPPER postgresql;
|
||||
-- test asynchronous notifications
|
||||
SELECT dblink_connect('dbname=contrib_regression');
|
||||
dblink_connect
|
||||
|
@ -361,9 +361,17 @@ 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 SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw
|
||||
OPTIONS (invalid 'val'); -- fail, invalid option
|
||||
CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw
|
||||
OPTIONS (password 'val'); -- fail, can't specify password here
|
||||
CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw
|
||||
OPTIONS (dbname 'contrib_regression');
|
||||
|
||||
CREATE USER MAPPING FOR public SERVER fdtest
|
||||
OPTIONS (server 'localhost'); -- fail, can't specify server here
|
||||
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;
|
||||
|
||||
@ -381,7 +389,6 @@ REVOKE EXECUTE ON FUNCTION dblink_connect_u(text, text) FROM dblink_regression_t
|
||||
DROP USER dblink_regression_test;
|
||||
DROP USER MAPPING FOR public SERVER fdtest;
|
||||
DROP SERVER fdtest;
|
||||
DROP FOREIGN DATA WRAPPER postgresql;
|
||||
|
||||
-- test asynchronous notifications
|
||||
SELECT dblink_connect('dbname=contrib_regression');
|
||||
|
@ -46,12 +46,10 @@ dblink_connect(text connname, text connstr) returns text
|
||||
|
||||
<para>
|
||||
The connection string may also be the name of an existing foreign
|
||||
server. It is recommended to use
|
||||
the <function>postgresql_fdw_validator</function> when defining
|
||||
the corresponding foreign-data wrapper. See the example below, as
|
||||
well as the following:
|
||||
server. It is recommended to use the foreign-data wrapper
|
||||
<literal>dblink_fdw</literal> when defining the corresponding foreign
|
||||
server. See the example below, as well as the following:
|
||||
<simplelist type="inline">
|
||||
<member><xref linkend="sql-createforeigndatawrapper"></member>
|
||||
<member><xref linkend="sql-createserver"></member>
|
||||
<member><xref linkend="sql-createusermapping"></member>
|
||||
</simplelist>
|
||||
@ -136,8 +134,7 @@ SELECT dblink_connect('myconn', 'dbname=postgres');
|
||||
-- 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 VALIDATOR postgresql_fdw_validator;
|
||||
CREATE SERVER fdtest FOREIGN DATA WRAPPER postgresql OPTIONS (hostaddr '127.0.0.1', dbname 'contrib_regression');
|
||||
CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw 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;
|
||||
@ -173,7 +170,6 @@ 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;
|
||||
</screen>
|
||||
</refsect1>
|
||||
</refentry>
|
||||
|
Loading…
Reference in New Issue
Block a user