diff options
author | Tom Lane | 2012-10-10 20:53:08 +0000 |
---|---|---|
committer | Tom Lane | 2012-10-10 20:53:08 +0000 |
commit | 8255566f9d479fdfeea09da3141d26afdbb5abe2 (patch) | |
tree | 00d54179dbdb5ea74c7fc782b001400993ff894b /contrib/dblink/dblink.c | |
parent | 392b2e5010ddce24bcb1a9d38adb1d06f01fb543 (diff) |
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
Diffstat (limited to 'contrib/dblink/dblink.c')
-rw-r--r-- | contrib/dblink/dblink.c | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index 9f1dac899e..ceec6ff813 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(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; +} |