diff --git a/contrib/dblink/Makefile b/contrib/dblink/Makefile index ac63748..a27db88 100644 --- a/contrib/dblink/Makefile +++ b/contrib/dblink/Makefile @@ -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 diff --git a/contrib/dblink/dblink--1.0--1.1.sql b/contrib/dblink/dblink--1.0--1.1.sql new file mode 100644 index 0000000..f224d3d --- /dev/null +++ b/contrib/dblink/dblink--1.0--1.1.sql @@ -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 IMMUTABLE; + +CREATE FOREIGN DATA WRAPPER dblink_fdw VALIDATOR dblink_fdw_validator; diff --git a/contrib/dblink/dblink--1.0.sql b/contrib/dblink/dblink--1.0.sql deleted file mode 100644 index 1fec9e3..0000000 --- a/contrib/dblink/dblink--1.0.sql +++ /dev/null @@ -1,223 +0,0 @@ -/* contrib/dblink/dblink--1.0.sql */ - --- complain if script is sourced in psql, rather than via CREATE EXTENSION -\echo Use "CREATE EXTENSION dblink" to load this file. \quit - --- dblink_connect now restricts non-superusers to password --- authenticated connections -CREATE FUNCTION dblink_connect (text) -RETURNS text -AS 'MODULE_PATHNAME','dblink_connect' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_connect (text, text) -RETURNS text -AS 'MODULE_PATHNAME','dblink_connect' -LANGUAGE C STRICT; - --- dblink_connect_u allows non-superusers to use --- non-password authenticated connections, but initially --- privileges are revoked from public -CREATE FUNCTION dblink_connect_u (text) -RETURNS text -AS 'MODULE_PATHNAME','dblink_connect' -LANGUAGE C STRICT SECURITY DEFINER; - -CREATE FUNCTION dblink_connect_u (text, text) -RETURNS text -AS 'MODULE_PATHNAME','dblink_connect' -LANGUAGE C STRICT SECURITY DEFINER; - -REVOKE ALL ON FUNCTION dblink_connect_u (text) FROM public; -REVOKE ALL ON FUNCTION dblink_connect_u (text, text) FROM public; - -CREATE FUNCTION dblink_disconnect () -RETURNS text -AS 'MODULE_PATHNAME','dblink_disconnect' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_disconnect (text) -RETURNS text -AS 'MODULE_PATHNAME','dblink_disconnect' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_open (text, text) -RETURNS text -AS 'MODULE_PATHNAME','dblink_open' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_open (text, text, boolean) -RETURNS text -AS 'MODULE_PATHNAME','dblink_open' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_open (text, text, text) -RETURNS text -AS 'MODULE_PATHNAME','dblink_open' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_open (text, text, text, boolean) -RETURNS text -AS 'MODULE_PATHNAME','dblink_open' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_fetch (text, int) -RETURNS setof record -AS 'MODULE_PATHNAME','dblink_fetch' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_fetch (text, int, boolean) -RETURNS setof record -AS 'MODULE_PATHNAME','dblink_fetch' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_fetch (text, text, int) -RETURNS setof record -AS 'MODULE_PATHNAME','dblink_fetch' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_fetch (text, text, int, boolean) -RETURNS setof record -AS 'MODULE_PATHNAME','dblink_fetch' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_close (text) -RETURNS text -AS 'MODULE_PATHNAME','dblink_close' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_close (text, boolean) -RETURNS text -AS 'MODULE_PATHNAME','dblink_close' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_close (text, text) -RETURNS text -AS 'MODULE_PATHNAME','dblink_close' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_close (text, text, boolean) -RETURNS text -AS 'MODULE_PATHNAME','dblink_close' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink (text, text) -RETURNS setof record -AS 'MODULE_PATHNAME','dblink_record' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink (text, text, boolean) -RETURNS setof record -AS 'MODULE_PATHNAME','dblink_record' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink (text) -RETURNS setof record -AS 'MODULE_PATHNAME','dblink_record' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink (text, boolean) -RETURNS setof record -AS 'MODULE_PATHNAME','dblink_record' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_exec (text, text) -RETURNS text -AS 'MODULE_PATHNAME','dblink_exec' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_exec (text, text, boolean) -RETURNS text -AS 'MODULE_PATHNAME','dblink_exec' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_exec (text) -RETURNS text -AS 'MODULE_PATHNAME','dblink_exec' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_exec (text,boolean) -RETURNS text -AS 'MODULE_PATHNAME','dblink_exec' -LANGUAGE C STRICT; - -CREATE TYPE dblink_pkey_results AS (position int, colname text); - -CREATE FUNCTION dblink_get_pkey (text) -RETURNS setof dblink_pkey_results -AS 'MODULE_PATHNAME','dblink_get_pkey' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_build_sql_insert (text, int2vector, int, _text, _text) -RETURNS text -AS 'MODULE_PATHNAME','dblink_build_sql_insert' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_build_sql_delete (text, int2vector, int, _text) -RETURNS text -AS 'MODULE_PATHNAME','dblink_build_sql_delete' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_build_sql_update (text, int2vector, int, _text, _text) -RETURNS text -AS 'MODULE_PATHNAME','dblink_build_sql_update' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_current_query () -RETURNS text -AS 'MODULE_PATHNAME','dblink_current_query' -LANGUAGE C; - -CREATE FUNCTION dblink_send_query(text, text) -RETURNS int4 -AS 'MODULE_PATHNAME', 'dblink_send_query' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_is_busy(text) -RETURNS int4 -AS 'MODULE_PATHNAME', 'dblink_is_busy' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_get_result(text) -RETURNS SETOF record -AS 'MODULE_PATHNAME', 'dblink_get_result' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_get_result(text, bool) -RETURNS SETOF record -AS 'MODULE_PATHNAME', 'dblink_get_result' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_get_connections() -RETURNS text[] -AS 'MODULE_PATHNAME', 'dblink_get_connections' -LANGUAGE C; - -CREATE FUNCTION dblink_cancel_query(text) -RETURNS text -AS 'MODULE_PATHNAME', 'dblink_cancel_query' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_error_message(text) -RETURNS text -AS 'MODULE_PATHNAME', 'dblink_error_message' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_get_notify( - OUT notify_name TEXT, - OUT be_pid INT4, - OUT extra TEXT -) -RETURNS setof record -AS 'MODULE_PATHNAME', 'dblink_get_notify' -LANGUAGE C STRICT; - -CREATE FUNCTION dblink_get_notify( - conname TEXT, - OUT notify_name TEXT, - OUT be_pid INT4, - OUT extra TEXT -) -RETURNS setof record -AS 'MODULE_PATHNAME', 'dblink_get_notify' -LANGUAGE C STRICT; diff --git a/contrib/dblink/dblink--1.1.sql b/contrib/dblink/dblink--1.1.sql new file mode 100644 index 0000000..e23b5c9 --- /dev/null +++ b/contrib/dblink/dblink--1.1.sql @@ -0,0 +1,234 @@ +/* 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 + +-- dblink_connect now restricts non-superusers to password +-- authenticated connections +CREATE FUNCTION dblink_connect (text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_connect' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_connect (text, text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_connect' +LANGUAGE C STRICT; + +-- dblink_connect_u allows non-superusers to use +-- non-password authenticated connections, but initially +-- privileges are revoked from public +CREATE FUNCTION dblink_connect_u (text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_connect' +LANGUAGE C STRICT SECURITY DEFINER; + +CREATE FUNCTION dblink_connect_u (text, text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_connect' +LANGUAGE C STRICT SECURITY DEFINER; + +REVOKE ALL ON FUNCTION dblink_connect_u (text) FROM public; +REVOKE ALL ON FUNCTION dblink_connect_u (text, text) FROM public; + +CREATE FUNCTION dblink_disconnect () +RETURNS text +AS 'MODULE_PATHNAME','dblink_disconnect' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_disconnect (text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_disconnect' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_open (text, text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_open' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_open (text, text, boolean) +RETURNS text +AS 'MODULE_PATHNAME','dblink_open' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_open (text, text, text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_open' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_open (text, text, text, boolean) +RETURNS text +AS 'MODULE_PATHNAME','dblink_open' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_fetch (text, int) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_fetch' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_fetch (text, int, boolean) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_fetch' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_fetch (text, text, int) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_fetch' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_fetch (text, text, int, boolean) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_fetch' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_close (text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_close' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_close (text, boolean) +RETURNS text +AS 'MODULE_PATHNAME','dblink_close' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_close (text, text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_close' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_close (text, text, boolean) +RETURNS text +AS 'MODULE_PATHNAME','dblink_close' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink (text, text) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_record' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink (text, text, boolean) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_record' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink (text) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_record' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink (text, boolean) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_record' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_exec (text, text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_exec' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_exec (text, text, boolean) +RETURNS text +AS 'MODULE_PATHNAME','dblink_exec' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_exec (text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_exec' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_exec (text,boolean) +RETURNS text +AS 'MODULE_PATHNAME','dblink_exec' +LANGUAGE C STRICT; + +CREATE TYPE dblink_pkey_results AS (position int, colname text); + +CREATE FUNCTION dblink_get_pkey (text) +RETURNS setof dblink_pkey_results +AS 'MODULE_PATHNAME','dblink_get_pkey' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_build_sql_insert (text, int2vector, int, _text, _text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_build_sql_insert' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_build_sql_delete (text, int2vector, int, _text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_build_sql_delete' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_build_sql_update (text, int2vector, int, _text, _text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_build_sql_update' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_current_query () +RETURNS text +AS 'MODULE_PATHNAME','dblink_current_query' +LANGUAGE C; + +CREATE FUNCTION dblink_send_query(text, text) +RETURNS int4 +AS 'MODULE_PATHNAME', 'dblink_send_query' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_is_busy(text) +RETURNS int4 +AS 'MODULE_PATHNAME', 'dblink_is_busy' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_get_result(text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'dblink_get_result' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_get_result(text, bool) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'dblink_get_result' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_get_connections() +RETURNS text[] +AS 'MODULE_PATHNAME', 'dblink_get_connections' +LANGUAGE C; + +CREATE FUNCTION dblink_cancel_query(text) +RETURNS text +AS 'MODULE_PATHNAME', 'dblink_cancel_query' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_error_message(text) +RETURNS text +AS 'MODULE_PATHNAME', 'dblink_error_message' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_get_notify( + OUT notify_name TEXT, + OUT be_pid INT4, + OUT extra TEXT +) +RETURNS setof record +AS 'MODULE_PATHNAME', 'dblink_get_notify' +LANGUAGE C STRICT; + +CREATE FUNCTION dblink_get_notify( + conname TEXT, + OUT notify_name TEXT, + OUT be_pid INT4, + OUT extra TEXT +) +RETURNS setof record +AS 'MODULE_PATHNAME', 'dblink_get_notify' +LANGUAGE C STRICT; + +/* stuffs added since 1.1 */ +CREATE FUNCTION dblink_fdw_validator( + options text[], + catalog oid +) +RETURNS void +AS 'MODULE_PATHNAME', 'dblink_fdw_validator' +LANGUAGE C IMMUTABLE; + +CREATE FOREIGN DATA WRAPPER dblink_fdw VALIDATOR dblink_fdw_validator; diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index 9f1dac8..6545cd2 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -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(PQconninfoOption *options, + const char *option, Oid context); /* Global */ static remoteConn *pconn = NULL; @@ -1912,6 +1917,76 @@ dblink_get_notify(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Validate the generic option given to SERVER or USER MAPPING. + * Raise an ERROR if the option is considered invalid. + * + * We just validate the name of options here, so semantic errors of 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; + PQconninfoOption *options = NULL; + + /* + * Get list of valid libpq options. It contains default values too, but we + * don't care the values. Obtained list is allocated with malloc, so we + * must free it before leaving this function. + */ + options = PQconndefaults(); + if (options == NULL) + ereport(ERROR, + (errcode(ERRCODE_FDW_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("could not get libpq default connection options"))); + PG_TRY(); + { + /* Validate each supplied option. */ + foreach(cell, options_list) + { + PQconninfoOption *opt; + DefElem *def = lfirst(cell); + StringInfoData buf; + + if (!is_valid_dblink_option(options, def->defname, context)) + { + /* + * Unknown option, or invalid option for the context specified, + * complain about it. Provide a hint with list of valid + * options for the object. + */ + 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_CATCH(); + { + free(options); + PG_RE_THROW(); + } + PG_END_TRY(); + + free(options); + PG_RETURN_VOID(); +} + /************************************************************* * internal functions */ @@ -2768,3 +2843,52 @@ validate_pkattnums(Relation rel, errmsg("invalid attribute number %d", pkattnum))); } } + +/* + * Check if the provided option is one of valid dblink options. Valid libpq + * conninfo options are classified as below: + * + * every debug options : invalid in every context + * every secure options: valid in only USER MAPPING options + * "user" : valid in only USER MAPPING options + * Others : valid in both SERVER options and USER MAPPING options + * + * Note that we accept client_encoding as options, but it is always ignored + * because we override it with calling PQclientEncoding to ensure that proper + * conversion is used. + */ +static bool +is_valid_dblink_option(PQconninfoOption *options, const char *option, + Oid context) +{ + PQconninfoOption *opt; + + for (opt = options; opt->keyword; opt++) + if (strcmp(opt->keyword, option) == 0) + break; + if (opt->keyword == NULL) + return false; + + /* + * All debug options "replication" is used in walreceiver. + */ + if (strcmp(opt->dispchar, "D") == 0) + return false; + + /* + * If the options is secure one or "user", it should be specified in only + * USER MAPPING options. Others should be specified in only SERVER options. + */ + if (strcmp(opt->keyword, "user") == 0 || strcmp(opt->dispchar, "*") == 0) + { + if (context != UserMappingRelationId) + return false; + } + else + { + if (context != ForeignServerRelationId) + return false; + } + + return true; +} diff --git a/contrib/dblink/dblink.control b/contrib/dblink/dblink.control index 4333a9b..39f439a 100644 --- a/contrib/dblink/dblink.control +++ b/contrib/dblink/dblink.control @@ -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 diff --git a/contrib/dblink/dblink.h b/contrib/dblink/dblink.h index 935d283..fd11658 100644 --- a/contrib/dblink/dblink.h +++ b/contrib/dblink/dblink.h @@ -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 */ diff --git a/contrib/dblink/expected/dblink.out b/contrib/dblink/expected/dblink.out index a1899ed..1cc06e3 100644 --- a/contrib/dblink/expected/dblink.out +++ b/contrib/dblink/expected/dblink.out @@ -783,8 +783,16 @@ 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'); -- ERROR +ERROR: invalid option "invalid" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, hostaddr, port, client_encoding, 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'); -- ERROR +ERROR: invalid option "password" +HINT: Valid options in this context are: service, connect_timeout, dbname, host, hostaddr, port, client_encoding, 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'); -- ERROR +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 +831,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 diff --git a/contrib/dblink/sql/dblink.sql b/contrib/dblink/sql/dblink.sql index 8c8ffe2..171ee50 100644 --- a/contrib/dblink/sql/dblink.sql +++ b/contrib/dblink/sql/dblink.sql @@ -361,8 +361,10 @@ 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'); -- ERROR +CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw OPTIONS (password 'val'); -- ERROR +CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw OPTIONS (dbname 'contrib_regression'); +CREATE USER MAPPING FOR public SERVER fdtest OPTIONS (server 'localhost'); -- ERROR 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 +383,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'); diff --git a/doc/src/sgml/dblink.sgml b/doc/src/sgml/dblink.sgml index 72ca765..186ab86 100644 --- a/doc/src/sgml/dblink.sgml +++ b/doc/src/sgml/dblink.sgml @@ -46,12 +46,10 @@ dblink_connect(text connname, text connstr) returns text The connection string may also be the name of an existing foreign - server. It is recommended to use - the postgresql_fdw_validator 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 + dblink_fdw when defining the corresponding foreign + server. See the example below, as well as the following: - @@ -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; diff --git a/doc/src/sgml/ref/create_foreign_data_wrapper.sgml b/doc/src/sgml/ref/create_foreign_data_wrapper.sgml index 804fb47..d9936e8 100644 --- a/doc/src/sgml/ref/create_foreign_data_wrapper.sgml +++ b/doc/src/sgml/ref/create_foreign_data_wrapper.sgml @@ -121,14 +121,6 @@ CREATE FOREIGN DATA WRAPPER name There is no support for updating a foreign table, and optimization of queries is primitive (and mostly left to the wrapper, too). - - - There is one built-in foreign-data wrapper validator function - provided: - postgresql_fdw_validator, which accepts - options corresponding to libpq connection - parameters. - diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c index d8845aa..8400db4 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -27,7 +27,6 @@ extern Datum pg_options_to_table(PG_FUNCTION_ARGS); -extern Datum postgresql_fdw_validator(PG_FUNCTION_ARGS); /* @@ -434,105 +433,6 @@ pg_options_to_table(PG_FUNCTION_ARGS) /* - * Describes the valid options for postgresql FDW, server, and user mapping. - */ -struct ConnectionOption -{ - const char *optname; - Oid optcontext; /* Oid of catalog in which option may appear */ -}; - -/* - * Copied from fe-connect.c PQconninfoOptions. - * - * The list is small - don't bother with bsearch if it stays so. - */ -static struct ConnectionOption libpq_conninfo_options[] = { - {"authtype", ForeignServerRelationId}, - {"service", ForeignServerRelationId}, - {"user", UserMappingRelationId}, - {"password", UserMappingRelationId}, - {"connect_timeout", ForeignServerRelationId}, - {"dbname", ForeignServerRelationId}, - {"host", ForeignServerRelationId}, - {"hostaddr", ForeignServerRelationId}, - {"port", ForeignServerRelationId}, - {"tty", ForeignServerRelationId}, - {"options", ForeignServerRelationId}, - {"requiressl", ForeignServerRelationId}, - {"sslmode", ForeignServerRelationId}, - {"gsslib", ForeignServerRelationId}, - {NULL, InvalidOid} -}; - - -/* - * Check if the provided option is one of libpq conninfo options. - * context is the Oid of the catalog the option came from, or 0 if we - * don't care. - */ -static bool -is_conninfo_option(const char *option, Oid context) -{ - struct ConnectionOption *opt; - - for (opt = libpq_conninfo_options; opt->optname; opt++) - if (context == opt->optcontext && strcmp(opt->optname, option) == 0) - return true; - return false; -} - - -/* - * Validate the generic option given to SERVER or USER MAPPING. - * Raise an ERROR if the option or its value is considered - * invalid. - * - * Valid server options are all libpq conninfo options except - * user and password -- these may only appear in USER MAPPING options. - */ -Datum -postgresql_fdw_validator(PG_FUNCTION_ARGS) -{ - List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); - Oid catalog = PG_GETARG_OID(1); - - ListCell *cell; - - foreach(cell, options_list) - { - DefElem *def = lfirst(cell); - - if (!is_conninfo_option(def->defname, catalog)) - { - struct ConnectionOption *opt; - StringInfoData buf; - - /* - * Unknown option specified, complain about it. Provide a hint - * with list of valid options for the object. - */ - initStringInfo(&buf); - for (opt = libpq_conninfo_options; opt->optname; opt++) - if (catalog == opt->optcontext) - appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", - opt->optname); - - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("invalid option \"%s\"", def->defname), - errhint("Valid options in this context are: %s", - buf.data))); - - PG_RETURN_BOOL(false); - } - } - - PG_RETURN_BOOL(true); -} - - -/* * get_foreign_data_wrapper_oid - given a FDW name, look up the OID * * If missing_ok is false, throw an error if name not found. If true, just diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 77a3b41..67a488e8 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -3433,9 +3433,6 @@ DESCR("filenode identifier of relation"); DATA(insert OID = 3034 ( pg_relation_filepath PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "2205" _null_ _null_ _null_ _null_ pg_relation_filepath _null_ _null_ _null_ )); DESCR("file path of relation"); -DATA(insert OID = 2316 ( postgresql_fdw_validator PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "1009 26" _null_ _null_ _null_ _null_ postgresql_fdw_validator _null_ _null_ _null_)); -DESCR("(internal)"); - DATA(insert OID = 2290 ( record_in PGNSP PGUID 12 1 0 0 0 f f f f t f s 3 0 2249 "2275 26 23" _null_ _null_ _null_ _null_ record_in _null_ _null_ _null_ )); DESCR("I/O"); DATA(insert OID = 2291 ( record_out PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 2275 "2249" _null_ _null_ _null_ _null_ record_out _null_ _null_ _null_ )); diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out index 1bfdca1..0a5b525 100644 --- a/src/test/regress/expected/foreign_data.out +++ b/src/test/regress/expected/foreign_data.out @@ -13,6 +13,40 @@ CREATE ROLE regress_test_role2; CREATE ROLE regress_test_role_super SUPERUSER; CREATE ROLE regress_test_indirect; CREATE ROLE unprivileged_role; +-- create validator for this test +CREATE OR REPLACE FUNCTION postgresql_fdw_validator(params text[], oid oid) RETURNS bool AS $$ +DECLARE + c text; + val text[]; +BEGIN + -- validate each option + FOR c IN SELECT unnest(params) LOOP + -- separate key and value + SELECT regexp_split_to_array(c, '=') INTO val; + RAISE DEBUG 'record %=%', val[1], val[2]; + + IF oid = 'pg_catalog.pg_foreign_server'::regclass THEN + IF val[1] NOT IN ('authtype', 'service', 'connect_timeout', + 'dbname', 'host', 'hostaddr', 'port', 'tty', 'options', + 'requiressl', 'sslmode', 'gsslib') THEN + RAISE 'invalid option "%"', val[1] + USING HINT = 'Valid options in this context are: authtype, service, connect_timeout, dbname, host, hostaddr, port, tty, options, requiressl, sslmode, gsslib'; + END IF; + ELSIF oid = 'pg_catalog.pg_user_mapping'::regclass THEN + IF val[1] NOT IN ('user', 'password') THEN + RAISE 'invalid option "%"', val[1] + USING HINT = 'Valid options in this context are: user, password'; + END IF; + ELSE + RAISE 'invalid option "%"', val[1] + USING HINT = 'Valid options in this context are: '; + END IF; + + END LOOP; + + RETURN true; +END; +$$ language plpgsql; CREATE FOREIGN DATA WRAPPER dummy; COMMENT ON FOREIGN DATA WRAPPER dummy IS 'useless'; CREATE FOREIGN DATA WRAPPER postgresql VALIDATOR postgresql_fdw_validator; @@ -1202,6 +1236,7 @@ DROP ROLE regress_test_role2; DROP FOREIGN DATA WRAPPER postgresql CASCADE; DROP FOREIGN DATA WRAPPER dummy CASCADE; NOTICE: drop cascades to server s0 +DROP FUNCTION postgresql_fdw_validator(text[], oid); \c DROP ROLE foreign_data_user; -- At this point we should have no wrappers, no servers, and no mappings. diff --git a/src/test/regress/sql/foreign_data.sql b/src/test/regress/sql/foreign_data.sql index 38057db..46e358b 100644 --- a/src/test/regress/sql/foreign_data.sql +++ b/src/test/regress/sql/foreign_data.sql @@ -20,6 +20,41 @@ CREATE ROLE regress_test_role_super SUPERUSER; CREATE ROLE regress_test_indirect; CREATE ROLE unprivileged_role; +-- create validator for this test +CREATE OR REPLACE FUNCTION postgresql_fdw_validator(params text[], oid oid) RETURNS bool AS $$ +DECLARE + c text; + val text[]; +BEGIN + -- validate each option + FOR c IN SELECT unnest(params) LOOP + -- separate key and value + SELECT regexp_split_to_array(c, '=') INTO val; + RAISE DEBUG 'record %=%', val[1], val[2]; + + IF oid = 'pg_catalog.pg_foreign_server'::regclass THEN + IF val[1] NOT IN ('authtype', 'service', 'connect_timeout', + 'dbname', 'host', 'hostaddr', 'port', 'tty', 'options', + 'requiressl', 'sslmode', 'gsslib') THEN + RAISE 'invalid option "%"', val[1] + USING HINT = 'Valid options in this context are: authtype, service, connect_timeout, dbname, host, hostaddr, port, tty, options, requiressl, sslmode, gsslib'; + END IF; + ELSIF oid = 'pg_catalog.pg_user_mapping'::regclass THEN + IF val[1] NOT IN ('user', 'password') THEN + RAISE 'invalid option "%"', val[1] + USING HINT = 'Valid options in this context are: user, password'; + END IF; + ELSE + RAISE 'invalid option "%"', val[1] + USING HINT = 'Valid options in this context are: '; + END IF; + + END LOOP; + + RETURN true; +END; +$$ language plpgsql; + CREATE FOREIGN DATA WRAPPER dummy; COMMENT ON FOREIGN DATA WRAPPER dummy IS 'useless'; CREATE FOREIGN DATA WRAPPER postgresql VALIDATOR postgresql_fdw_validator; @@ -497,6 +532,7 @@ DROP ROLE unprivileged_role; DROP ROLE regress_test_role2; DROP FOREIGN DATA WRAPPER postgresql CASCADE; DROP FOREIGN DATA WRAPPER dummy CASCADE; +DROP FUNCTION postgresql_fdw_validator(text[], oid); \c DROP ROLE foreign_data_user;