mirror of
https://github.com/postgres/postgres.git
synced 2025-06-11 20:28:21 +03:00
Add postgres_fdw contrib module.
There's still a lot of room for improvement, but it basically works, and we need this to be present before we can do anything much with the writable-foreign-tables patch. So let's commit it and get on with testing. Shigeru Hanada, reviewed by KaiGai Kohei and Tom Lane
This commit is contained in:
293
contrib/postgres_fdw/option.c
Normal file
293
contrib/postgres_fdw/option.c
Normal file
@ -0,0 +1,293 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* option.c
|
||||
* FDW option handling for postgres_fdw
|
||||
*
|
||||
* Portions Copyright (c) 2012-2013, PostgreSQL Global Development Group
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* contrib/postgres_fdw/option.c
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "postgres_fdw.h"
|
||||
|
||||
#include "access/reloptions.h"
|
||||
#include "catalog/pg_foreign_server.h"
|
||||
#include "catalog/pg_foreign_table.h"
|
||||
#include "catalog/pg_user_mapping.h"
|
||||
#include "commands/defrem.h"
|
||||
|
||||
|
||||
/*
|
||||
* Describes the valid options for objects that this wrapper uses.
|
||||
*/
|
||||
typedef struct PgFdwOption
|
||||
{
|
||||
const char *keyword;
|
||||
Oid optcontext; /* OID of catalog in which option may appear */
|
||||
bool is_libpq_opt; /* true if it's used in libpq */
|
||||
} PgFdwOption;
|
||||
|
||||
/*
|
||||
* Valid options for postgres_fdw.
|
||||
* Allocated and filled in InitPgFdwOptions.
|
||||
*/
|
||||
static PgFdwOption *postgres_fdw_options;
|
||||
|
||||
/*
|
||||
* Valid options for libpq.
|
||||
* Allocated and filled in InitPgFdwOptions.
|
||||
*/
|
||||
static PQconninfoOption *libpq_options;
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
*/
|
||||
static void InitPgFdwOptions(void);
|
||||
static bool is_valid_option(const char *keyword, Oid context);
|
||||
static bool is_libpq_option(const char *keyword);
|
||||
|
||||
|
||||
/*
|
||||
* Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER,
|
||||
* USER MAPPING or FOREIGN TABLE that uses postgres_fdw.
|
||||
*
|
||||
* Raise an ERROR if the option or its value is considered invalid.
|
||||
*/
|
||||
extern Datum postgres_fdw_validator(PG_FUNCTION_ARGS);
|
||||
|
||||
PG_FUNCTION_INFO_V1(postgres_fdw_validator);
|
||||
|
||||
Datum
|
||||
postgres_fdw_validator(PG_FUNCTION_ARGS)
|
||||
{
|
||||
List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
|
||||
Oid catalog = PG_GETARG_OID(1);
|
||||
ListCell *cell;
|
||||
|
||||
/* Build our options lists if we didn't yet. */
|
||||
InitPgFdwOptions();
|
||||
|
||||
/*
|
||||
* Check that only options supported by postgres_fdw, and allowed for the
|
||||
* current object type, are given.
|
||||
*/
|
||||
foreach(cell, options_list)
|
||||
{
|
||||
DefElem *def = (DefElem *) lfirst(cell);
|
||||
|
||||
if (!is_valid_option(def->defname, catalog))
|
||||
{
|
||||
/*
|
||||
* Unknown option specified, complain about it. Provide a hint
|
||||
* with list of valid options for the object.
|
||||
*/
|
||||
PgFdwOption *opt;
|
||||
StringInfoData buf;
|
||||
|
||||
initStringInfo(&buf);
|
||||
for (opt = postgres_fdw_options; opt->keyword; opt++)
|
||||
{
|
||||
if (catalog == opt->optcontext)
|
||||
appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
|
||||
opt->keyword);
|
||||
}
|
||||
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
|
||||
errmsg("invalid option \"%s\"", def->defname),
|
||||
errhint("Valid options in this context are: %s",
|
||||
buf.data)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate option value, when we can do so without any context.
|
||||
*/
|
||||
if (strcmp(def->defname, "use_remote_explain") == 0)
|
||||
{
|
||||
/* use_remote_explain accepts only boolean values */
|
||||
(void) defGetBoolean(def);
|
||||
}
|
||||
else if (strcmp(def->defname, "fdw_startup_cost") == 0 ||
|
||||
strcmp(def->defname, "fdw_tuple_cost") == 0)
|
||||
{
|
||||
/* these must have a non-negative numeric value */
|
||||
double val;
|
||||
char *endp;
|
||||
|
||||
val = strtod(defGetString(def), &endp);
|
||||
if (*endp || val < 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("%s requires a non-negative numeric value",
|
||||
def->defname)));
|
||||
}
|
||||
}
|
||||
|
||||
PG_RETURN_VOID();
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize option lists.
|
||||
*/
|
||||
static void
|
||||
InitPgFdwOptions(void)
|
||||
{
|
||||
int num_libpq_opts;
|
||||
PQconninfoOption *lopt;
|
||||
PgFdwOption *popt;
|
||||
|
||||
/* non-libpq FDW-specific FDW options */
|
||||
static const PgFdwOption non_libpq_options[] = {
|
||||
{"schema_name", ForeignTableRelationId, false},
|
||||
{"table_name", ForeignTableRelationId, false},
|
||||
{"column_name", AttributeRelationId, false},
|
||||
/* use_remote_explain is available on both server and table */
|
||||
{"use_remote_explain", ForeignServerRelationId, false},
|
||||
{"use_remote_explain", ForeignTableRelationId, false},
|
||||
/* cost factors */
|
||||
{"fdw_startup_cost", ForeignServerRelationId, false},
|
||||
{"fdw_tuple_cost", ForeignServerRelationId, false},
|
||||
{NULL, InvalidOid, false}
|
||||
};
|
||||
|
||||
/* Prevent redundant initialization. */
|
||||
if (postgres_fdw_options)
|
||||
return;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
libpq_options = PQconndefaults();
|
||||
if (!libpq_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")));
|
||||
|
||||
/* Count how many libpq options are available. */
|
||||
num_libpq_opts = 0;
|
||||
for (lopt = libpq_options; lopt->keyword; lopt++)
|
||||
num_libpq_opts++;
|
||||
|
||||
/*
|
||||
* Construct an array which consists of all valid options for
|
||||
* postgres_fdw, by appending FDW-specific options to libpq options.
|
||||
*
|
||||
* We use plain malloc here to allocate postgres_fdw_options because it
|
||||
* lives as long as the backend process does. Besides, keeping
|
||||
* libpq_options in memory allows us to avoid copying every keyword
|
||||
* string.
|
||||
*/
|
||||
postgres_fdw_options = (PgFdwOption *)
|
||||
malloc(sizeof(PgFdwOption) * num_libpq_opts +
|
||||
sizeof(non_libpq_options));
|
||||
if (postgres_fdw_options == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FDW_OUT_OF_MEMORY),
|
||||
errmsg("out of memory")));
|
||||
|
||||
popt = postgres_fdw_options;
|
||||
for (lopt = libpq_options; lopt->keyword; lopt++)
|
||||
{
|
||||
/* Hide debug options, as well as settings we override internally. */
|
||||
if (strchr(lopt->dispchar, 'D') ||
|
||||
strcmp(lopt->keyword, "fallback_application_name") == 0 ||
|
||||
strcmp(lopt->keyword, "client_encoding") == 0)
|
||||
continue;
|
||||
|
||||
/* We don't have to copy keyword string, as described above. */
|
||||
popt->keyword = lopt->keyword;
|
||||
|
||||
/*
|
||||
* "user" and any secret options are allowed only on user mappings.
|
||||
* Everything else is a server option.
|
||||
*/
|
||||
if (strcmp(lopt->keyword, "user") == 0 || strchr(lopt->dispchar, '*'))
|
||||
popt->optcontext = UserMappingRelationId;
|
||||
else
|
||||
popt->optcontext = ForeignServerRelationId;
|
||||
popt->is_libpq_opt = true;
|
||||
|
||||
popt++;
|
||||
}
|
||||
|
||||
/* Append FDW-specific options and dummy terminator. */
|
||||
memcpy(popt, non_libpq_options, sizeof(non_libpq_options));
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether the given option is one of the valid postgres_fdw options.
|
||||
* context is the Oid of the catalog holding the object the option is for.
|
||||
*/
|
||||
static bool
|
||||
is_valid_option(const char *keyword, Oid context)
|
||||
{
|
||||
PgFdwOption *opt;
|
||||
|
||||
Assert(postgres_fdw_options); /* must be initialized already */
|
||||
|
||||
for (opt = postgres_fdw_options; opt->keyword; opt++)
|
||||
{
|
||||
if (context == opt->optcontext && strcmp(opt->keyword, keyword) == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether the given option is one of the valid libpq options.
|
||||
*/
|
||||
static bool
|
||||
is_libpq_option(const char *keyword)
|
||||
{
|
||||
PgFdwOption *opt;
|
||||
|
||||
Assert(postgres_fdw_options); /* must be initialized already */
|
||||
|
||||
for (opt = postgres_fdw_options; opt->keyword; opt++)
|
||||
{
|
||||
if (opt->is_libpq_opt && strcmp(opt->keyword, keyword) == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate key-value arrays which include only libpq options from the
|
||||
* given list (which can contain any kind of options). Caller must have
|
||||
* allocated large-enough arrays. Returns number of options found.
|
||||
*/
|
||||
int
|
||||
ExtractConnectionOptions(List *defelems, const char **keywords,
|
||||
const char **values)
|
||||
{
|
||||
ListCell *lc;
|
||||
int i;
|
||||
|
||||
/* Build our options lists if we didn't yet. */
|
||||
InitPgFdwOptions();
|
||||
|
||||
i = 0;
|
||||
foreach(lc, defelems)
|
||||
{
|
||||
DefElem *d = (DefElem *) lfirst(lc);
|
||||
|
||||
if (is_libpq_option(d->defname))
|
||||
{
|
||||
keywords[i] = d->defname;
|
||||
values[i] = defGetString(d);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
Reference in New Issue
Block a user