Commit d0d75c40 authored by Tom Lane's avatar Tom Lane

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
parent f435cd1d
......@@ -43,6 +43,7 @@ SUBDIRS = \
pgcrypto \
pgrowlocks \
pgstattuple \
postgres_fdw \
seg \
spi \
tablefunc \
......
# Generated subdirectories
/log/
/results/
/tmp_check/
# contrib/postgres_fdw/Makefile
MODULE_big = postgres_fdw
OBJS = postgres_fdw.o option.o deparse.o connection.o
PG_CPPFLAGS = -I$(libpq_srcdir)
SHLIB_LINK = $(libpq)
SHLIB_PREREQS = submake-libpq
EXTENSION = postgres_fdw
DATA = postgres_fdw--1.0.sql
REGRESS = postgres_fdw
# the db name is hard-coded in the tests
override USE_MODULE_DB =
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = contrib/postgres_fdw
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*-------------------------------------------------------------------------
*
* 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;
}
/* contrib/postgres_fdw/postgres_fdw--1.0.sql */
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION postgres_fdw" to load this file. \quit
CREATE FUNCTION postgres_fdw_handler()
RETURNS fdw_handler
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;
CREATE FUNCTION postgres_fdw_validator(text[], oid)
RETURNS void
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;
CREATE FOREIGN DATA WRAPPER postgres_fdw
HANDLER postgres_fdw_handler
VALIDATOR postgres_fdw_validator;
This diff is collapsed.
# postgres_fdw extension
comment = 'foreign-data wrapper for remote PostgreSQL servers'
default_version = '1.0'
module_pathname = '$libdir/postgres_fdw'
relocatable = true
/*-------------------------------------------------------------------------
*
* postgres_fdw.h
* Foreign-data wrapper for remote PostgreSQL servers
*
* Portions Copyright (c) 2012-2013, PostgreSQL Global Development Group
*
* IDENTIFICATION
* contrib/postgres_fdw/postgres_fdw.h
*
*-------------------------------------------------------------------------
*/
#ifndef POSTGRES_FDW_H
#define POSTGRES_FDW_H
#include "foreign/foreign.h"
#include "lib/stringinfo.h"
#include "nodes/relation.h"
#include "utils/rel.h"
#include "libpq-fe.h"
/* in connection.c */
extern PGconn *GetConnection(ForeignServer *server, UserMapping *user);
extern void ReleaseConnection(PGconn *conn);
extern unsigned int GetCursorNumber(PGconn *conn);
extern void pgfdw_report_error(int elevel, PGresult *res, bool clear,
const char *sql);
/* in option.c */
extern int ExtractConnectionOptions(List *defelems,
const char **keywords,
const char **values);
/* in deparse.c */
extern void classifyConditions(PlannerInfo *root,
RelOptInfo *baserel,
List **remote_conds,
List **param_conds,
List **local_conds,
List **param_numbers);
extern void deparseSimpleSql(StringInfo buf,
PlannerInfo *root,
RelOptInfo *baserel,
List *local_conds);
extern void appendWhereClause(StringInfo buf,
bool has_where,
List *exprs,
PlannerInfo *root);
extern void deparseAnalyzeSql(StringInfo buf, Relation rel);
#endif /* POSTGRES_FDW_H */
This diff is collapsed.
......@@ -1098,7 +1098,7 @@ omicron bryanh guest1
<replaceable>servicename</> can be set on the server side using the
<xref linkend="guc-krb-srvname"> configuration parameter, and on the
client side using the <literal>krbsrvname</> connection parameter. (See
also <xref linkend="libpq-connect">.) The installation default can be
also <xref linkend="libpq-paramkeywords">.) The installation default can be
changed from the default <literal>postgres</literal> at build time using
<literal>./configure --with-krb-srvnam=</><replaceable>whatever</>.
In most environments,
......
......@@ -132,6 +132,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
&pgstatstatements;
&pgstattuple;
&pgtrgm;
&postgres-fdw;
&seg;
&sepgsql;
&contrib-spi;
......
......@@ -8,11 +8,16 @@
</indexterm>
<para>
<filename>dblink</> is a module which supports connections to
<filename>dblink</> is a module that supports connections to
other <productname>PostgreSQL</> databases from within a database
session.
</para>
<para>
See also <xref linkend="postgres-fdw">, which provides roughly the same
functionality using a more modern and standards-compliant infrastructure.
</para>
<refentry id="CONTRIB-DBLINK-CONNECT">
<refmeta>
<refentrytitle>dblink_connect</refentrytitle>
......@@ -47,12 +52,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 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-createserver"></member>
<member><xref linkend="sql-createusermapping"></member>
</simplelist>
<literal>dblink_fdw</literal> when defining the foreign
server. See the example below, as well as
<xref linkend="sql-createserver"> and
<xref linkend="sql-createusermapping">.
</para>
</refsect1>
......@@ -77,8 +80,8 @@ dblink_connect(text connname, text connstr) returns text
<para><application>libpq</>-style connection info string, for example
<literal>hostaddr=127.0.0.1 port=5432 dbname=mydb user=postgres
password=mypasswd</>.
For details see <function>PQconnectdb</> in
<xref linkend="libpq-connect">.
For details see <xref linkend="libpq-connstring">.
Alternatively, the name of a foreign server.
</para>
</listitem>
</varlistentry>
......@@ -133,9 +136,10 @@ SELECT dblink_connect('myconn', 'dbname=postgres');
-- ERROR: password is required
-- 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 SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw OPTIONS (hostaddr '127.0.0.1', dbname 'contrib_regression');
CREATE USER dblink_regression_test WITH PASSWORD 'secret';
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;
GRANT SELECT ON TABLE foo TO dblink_regression_test;
......@@ -166,7 +170,7 @@ SELECT * FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[])
\c - :ORIGINAL_USER
REVOKE USAGE ON FOREIGN SERVER fdtest FROM dblink_regression_test;
REVOKE SELECT ON TABLE foo FROM dblink_regression_test;
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;
......
......@@ -134,6 +134,7 @@
<!ENTITY pgtesttiming SYSTEM "pgtesttiming.sgml">
<!ENTITY pgtrgm SYSTEM "pgtrgm.sgml">
<!ENTITY pgupgrade SYSTEM "pgupgrade.sgml">
<!ENTITY postgres-fdw SYSTEM "postgres-fdw.sgml">
<!ENTITY seg SYSTEM "seg.sgml">
<!ENTITY contrib-spi SYSTEM "contrib-spi.sgml">
<!ENTITY sepgsql SYSTEM "sepgsql.sgml">
......
......@@ -6941,7 +6941,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
<para>
The file uses an <quote>INI file</quote> format where the section
name is the service name and the parameters are connection
parameters; see <xref linkend="libpq-connect"> for a list. For
parameters; see <xref linkend="libpq-paramkeywords"> for a list. For
example:
<programlisting>
# comment
......
This diff is collapsed.
......@@ -699,7 +699,7 @@ SELECT *
WHERE proname LIKE 'bytea%';
</programlisting>
The <xref linkend="CONTRIB-DBLINK-FUNCTION"> function
(part of the <xref linkend="dblink"> module>) executes
(part of the <xref linkend="dblink"> module) executes
a remote query. It is declared to return
<type>record</> since it might be used for any kind of query.
The actual column set must be specified in the calling query so
......
......@@ -314,8 +314,7 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
Specifies a connection string to be used for the standby server
to connect with the primary. This string is in the format
accepted by the libpq <function>PQconnectdb</function> function,
described in <xref linkend="libpq-connect">. If any option is
described in <xref linkend="libpq-connstring">. If any option is
unspecified in this string, then the corresponding environment
variable (see <xref linkend="libpq-envars">) is checked. If the
environment variable is not set either, then
......
......@@ -121,14 +121,6 @@ CREATE FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable>
There is no support for updating a foreign table, and optimization of
queries is primitive (and mostly left to the wrapper, too).
</para>
<para>
There is one built-in foreign-data wrapper validator function
provided:
<filename>postgresql_fdw_validator</filename>, which accepts
options corresponding to <application>libpq</> connection
parameters.
</para>
</refsect1>
<refsect1>
......
......@@ -32,7 +32,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
<title>Description</title>
<para>
<command>CREATE FOREIGN TABLE</command> will create a new foreign table
<command>CREATE FOREIGN TABLE</command> creates a new foreign table
in the current database. The table will be owned by the user issuing the
command.
</para>
......@@ -54,8 +54,9 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
</para>
<para>
To be able to create a table, you must have <literal>USAGE</literal>
privilege on all column types.
To be able to create a foreign table, you must have <literal>USAGE</literal>
privilege on the foreign server, as well as <literal>USAGE</literal>
privilege on all column types used in the table.
</para>
</refsect1>
......@@ -134,7 +135,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
<term><replaceable class="PARAMETER">server_name</replaceable></term>
<listitem>
<para>
The name of an existing server for the foreign table.
The name of an existing foreign server to use for the foreign table.
For details on defining a server, see <xref
linkend="SQL-CREATESERVER">.
</para>
......@@ -164,7 +165,8 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
<title>Examples</title>
<para>
Create foreign table <structname>films</> with <structname>film_server</>:
Create foreign table <structname>films</>, which will be accessed through
the server <structname>film_server</>:
<programlisting>
CREATE FOREIGN TABLE films (
......
......@@ -110,11 +110,10 @@ CREATE SERVER <replaceable class="parameter">server_name</replaceable> [ TYPE '<
<title>Notes</title>
<para>
When using the <application>dblink</application> module
(see <xref linkend="dblink">), the foreign server name can be used
When using the <xref linkend="dblink"> module,
a foreign server's name can be used
as an argument of the <xref linkend="contrib-dblink-connect">
function to indicate the connection parameters. See also there for
more examples. It is necessary to have
function to indicate the connection parameters. It is necessary to have
the <literal>USAGE</literal> privilege on the foreign server to be
able to use it in this way.
</para>
......@@ -124,19 +123,13 @@ CREATE SERVER <replaceable class="parameter">server_name</replaceable> [ TYPE '<
<title>Examples</title>
<para>
Create a server <literal>foo</> that uses the built-in foreign-data
wrapper <literal>default</>:
Create a server <literal>myserver</> that uses the
foreign-data wrapper <literal>postgres_fdw</>:
<programlisting>
CREATE SERVER foo FOREIGN DATA WRAPPER "default";
CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'foo', dbname 'foodb', port '5432');
</programlisting>
See <xref linkend="postgres-fdw"> for more details.
</para>
<para>
Create a server <literal>myserver</> that uses the
foreign-data wrapper <literal>pgsql</>:
<programlisting>
CREATE SERVER myserver FOREIGN DATA WRAPPER pgsql OPTIONS (host 'foo', dbname 'foodb', port '5432');
</programlisting></para>
</refsect1>
<refsect1>
......@@ -154,6 +147,7 @@ CREATE SERVER myserver FOREIGN DATA WRAPPER pgsql OPTIONS (host 'foo', dbname 'f
<member><xref linkend="sql-alterserver"></member>
<member><xref linkend="sql-dropserver"></member>
<member><xref linkend="sql-createforeigndatawrapper"></member>
<member><xref linkend="sql-createforeigntable"></member>
<member><xref linkend="sql-createusermapping"></member>
</simplelist>
</refsect1>
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment