summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--contrib/Makefile1
-rw-r--r--contrib/README3
-rw-r--r--contrib/postgresql_fdw/.gitignore3
-rw-r--r--contrib/postgresql_fdw/Makefile23
-rw-r--r--contrib/postgresql_fdw/connection.c356
-rw-r--r--contrib/postgresql_fdw/connection.h25
-rw-r--r--contrib/postgresql_fdw/deparse.c364
-rw-r--r--contrib/postgresql_fdw/deparse.h18
-rw-r--r--contrib/postgresql_fdw/expected/postgresql_fdw.out154
-rw-r--r--contrib/postgresql_fdw/postgresql_fdw--1.0.sql11
-rw-r--r--contrib/postgresql_fdw/postgresql_fdw.c366
-rw-r--r--contrib/postgresql_fdw/postgresql_fdw.control5
-rw-r--r--contrib/postgresql_fdw/sql/postgresql_fdw.sql88
-rw-r--r--doc/src/sgml/contrib.sgml1
-rw-r--r--doc/src/sgml/filelist.sgml1
-rw-r--r--doc/src/sgml/postgresql-fdw.sgml346
-rw-r--r--src/backend/foreign/foreign.c69
-rw-r--r--src/include/foreign/foreign.h1
18 files changed, 1811 insertions, 24 deletions
diff --git a/contrib/Makefile b/contrib/Makefile
index 0c238aae16..6daf8684ac 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -42,6 +42,7 @@ SUBDIRS = \
pgcrypto \
pgrowlocks \
pgstattuple \
+ postgresql_fdw \
seg \
spi \
tablefunc \
diff --git a/contrib/README b/contrib/README
index a1d42a11cb..32b95e3d31 100644
--- a/contrib/README
+++ b/contrib/README
@@ -163,6 +163,9 @@ pgstattuple -
space within a table
by Tatsuo Ishii <[email protected]>
+postgresql_fdw -
+ Foreign-data wrapper for external PostgreSQL server.
+
seg -
Confidence-interval datatype (GiST indexing example)
by Gene Selkov, Jr. <[email protected]>
diff --git a/contrib/postgresql_fdw/.gitignore b/contrib/postgresql_fdw/.gitignore
new file mode 100644
index 0000000000..4a8bf1781d
--- /dev/null
+++ b/contrib/postgresql_fdw/.gitignore
@@ -0,0 +1,3 @@
+/postgresql_fdw.sql
+# Generated subdirectories
+/results/
diff --git a/contrib/postgresql_fdw/Makefile b/contrib/postgresql_fdw/Makefile
new file mode 100644
index 0000000000..22dffc42dd
--- /dev/null
+++ b/contrib/postgresql_fdw/Makefile
@@ -0,0 +1,23 @@
+# contrib/postgresql_fdw/Makefile
+
+MODULE_big = postgresql_fdw
+PG_CPPFLAGS = -I$(libpq_srcdir)
+OBJS = postgresql_fdw.o connection.o deparse.o
+SHLIB_LINK = $(libpq)
+SHLIB_PREREQS = submake-libpq
+
+EXTENSION = postgresql_fdw
+DATA = postgresql_fdw--1.0.sql
+
+REGRESS = postgresql_fdw
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/postgresql_fdw
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/postgresql_fdw/connection.c b/contrib/postgresql_fdw/connection.c
new file mode 100644
index 0000000000..f12b798ad9
--- /dev/null
+++ b/contrib/postgresql_fdw/connection.c
@@ -0,0 +1,356 @@
+/*-------------------------------------------------------------------------
+ *
+ * connectoin.c
+ * Connection management for postgresql_fdw
+ *
+ * Portions Copyright (c) 2010-2011, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/postgresql_fdw/connection.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "foreign/foreign.h"
+#include "libpq-fe.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "utils/hsearch.h"
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+
+#include "connection.h"
+
+/* ============================================================================
+ * Connection management functions
+ * ==========================================================================*/
+
+/*
+ * Connection cache entry managed with hash table.
+ */
+typedef struct ConnCacheEntry
+{
+ /* hash key must be first */
+ char name[NAMEDATALEN]; /* connection name; used as hash key */
+ int refs; /* reference counter */
+ PGconn *conn; /* foreign server connection */
+} ConnCacheEntry;
+
+/*
+ * Hash table which is used to cache connection to PostgreSQL servers, will be
+ * initialized before first attempt to connect PostgreSQL server by the backend.
+ */
+static HTAB *FSConnectionHash;
+
+/* ----------------------------------------------------------------------------
+ * prototype of private functions
+ * --------------------------------------------------------------------------*/
+static void
+cleanup_connection(ResourceReleasePhase phase,
+ bool isCommit,
+ bool isTopLevel,
+ void *arg);
+static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
+/*
+ * Get a PGconn which can be used to execute foreign query on the remote
+ * PostgreSQL server with the user's authorization. If this was the first
+ * request for the server, new connection is established.
+ */
+PGconn *
+GetConnection(ForeignServer *server, UserMapping *user)
+{
+ const char *conname = server->servername;
+ bool found;
+ ConnCacheEntry *entry;
+ PGconn *conn = NULL;
+
+ /* initialize connection cache if it isn't */
+ if (FSConnectionHash == NULL)
+ {
+ HASHCTL ctl;
+
+ /* hash key is the name of the connection */
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = NAMEDATALEN;
+ ctl.entrysize = sizeof(ConnCacheEntry);
+ /* allocate FSConnectionHash in the cache context */
+ ctl.hcxt = CacheMemoryContext;
+ FSConnectionHash = hash_create("Foreign Connections", 32,
+ &ctl,
+ HASH_ELEM | HASH_CONTEXT);
+ }
+
+ /* Is there any cached and valid connection with such name? */
+ entry = hash_search(FSConnectionHash, conname, HASH_ENTER, &found);
+ if (found)
+ {
+ if (entry->conn != NULL)
+ {
+ entry->refs++;
+ elog(DEBUG3, "ref %d for %s", entry->refs, entry->name);
+ return entry->conn;
+ }
+
+ /*
+ * Connection cache entry was found but connection in it is invalid.
+ * We reuse entry to store newly established connection later.
+ */
+ }
+ else
+ {
+ /*
+ * Use ResourceOner to clean the connection up on error including
+ * user interrupt.
+ */
+ entry->refs = 0;
+ entry->conn = NULL;
+ RegisterResourceReleaseCallback(cleanup_connection, entry);
+ }
+
+ /*
+ * Here we have to establish new connection.
+ * Use PG_TRY block to ensure closing connection on error.
+ */
+ PG_TRY();
+ {
+ /* Connect to the foreign PostgreSQL server */
+ conn = connect_pg_server(server, user);
+
+ /*
+ * Initialize the cache entry to keep new connection.
+ * Note: entry->name has been initialized in hash_search(HASH_ENTER).
+ */
+ entry->refs = 1;
+ entry->conn = conn;
+ elog(DEBUG3, "connected to %s (%d)", entry->name, entry->refs);
+ }
+ PG_CATCH();
+ {
+ PQfinish(conn);
+ entry->refs = 0;
+ entry->conn = NULL;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ return conn;
+}
+
+/*
+ * For non-superusers, insist that the connstr specify a password. This
+ * prevents a password from being picked up from .pgpass, a service file,
+ * the environment, etc. We don't want the postgres user's passwords
+ * to be accessible to non-superusers.
+ */
+static void
+check_conn_params(const char **keywords, const char **values)
+{
+ int i;
+
+ /* no check required if superuser */
+ if (superuser())
+ return;
+
+ /* ok if params contain a non-empty password */
+ for (i = 0; keywords[i] != NULL; i++)
+ {
+ if (strcmp(keywords[i], "password") == 0 && values[i][0] != '\0')
+ return;
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
+ errmsg("password is required"),
+ errdetail("Non-superusers must provide a password in the connection string.")));
+}
+
+static int
+flatten_generic_options(List *defelems, const char **keywords, const char **values)
+{
+ ListCell *lc;
+ int i;
+
+ i = 0;
+ foreach(lc, defelems)
+ {
+ DefElem *d = (DefElem *) lfirst(lc);
+ keywords[i] = d->defname;
+ values[i] = strVal(d->arg);
+ i++;
+ }
+ return i;
+}
+
+static PGconn *
+connect_pg_server(ForeignServer *server, UserMapping *user)
+{
+ const char *conname = server->servername;
+ PGconn *conn;
+ const char **all_keywords;
+ const char **all_values;
+ const char **keywords;
+ const char **values;
+ int n;
+ int i, j;
+
+ /*
+ * Construct connection params from generic options of ForeignServer and
+ * UserMapping. Generic options might not be a one of connection options.
+ */
+ n = list_length(server->options) + list_length(user->options) + 1;
+ all_keywords = (const char **) palloc(sizeof(char *) * n);
+ all_values = (const char **) palloc(sizeof(char *) * n);
+ keywords = (const char **) palloc(sizeof(char *) * n);
+ values = (const char **) palloc(sizeof(char *) * n);
+ n = 0;
+ n += flatten_generic_options(server->options,
+ all_keywords + n, all_values + n);
+ n += flatten_generic_options(user->options,
+ all_keywords + n, all_values + n);
+ all_keywords[n] = all_values[n] = NULL;
+
+ for (i = 0, j = 0; all_keywords[i]; i++)
+ {
+ /* Use only valid libpq connection options. */
+ if (!is_conninfo_option(all_keywords[i]))
+ continue;
+
+ keywords[j] = all_keywords[i];
+ values[j] = all_values[i];
+ j++;
+ }
+ keywords[j] = values[j] = NULL;
+ pfree(all_keywords);
+ pfree(all_values);
+
+ /* TODO: omit newly added parameters when connecting to older server. */
+
+ /* verify connection parameters and do connect */
+ check_conn_params(keywords, values);
+ conn = PQconnectdbParams(keywords, values, 0);
+ if (!conn || PQstatus(conn) != CONNECTION_OK)
+ ereport(ERROR,
+ (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),
+ errmsg("could not connect to server \"%s\"", conname),
+ errdetail("%s", PQerrorMessage(conn))));
+ pfree(keywords);
+ pfree(values);
+
+ /*
+ * Check that non-superuser has used password to establish connection.
+ * This check logic is based on dblink_security_check() in contrib/dblink.
+ *
+ * XXX Should we check this even if we don't provide unsafe version like
+ * dblink_connect_u()?
+ */
+ if (!superuser() && !PQconnectionUsedPassword(conn))
+ {
+ PQfinish(conn);
+ ereport(ERROR,
+ (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
+ errmsg("password is required"),
+ errdetail("Non-superuser cannot connect if the server does not request a password."),
+ errhint("Target server's authentication method must be changed.")));
+ }
+
+ /*
+ * Set client_encoding of the connection so that libpq can convert encoding
+ * properly.
+ */
+ PQsetClientEncoding(conn, GetDatabaseEncodingName());
+
+ return conn;
+}
+
+/*
+ * Mark the connection as "unused", and close it if the caller was the last
+ * user of the connection.
+ */
+void
+ReleaseConnection(PGconn *conn)
+{
+ HASH_SEQ_STATUS scan;
+ ConnCacheEntry *entry;
+
+ if (conn == NULL)
+ return;
+
+ /*
+ * We need to scan seqencially since we use the address to find appropriate
+ * PGconn from the hash table.
+ */
+ hash_seq_init(&scan, FSConnectionHash);
+ while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
+ {
+ if (entry->conn == conn)
+ break;
+ }
+ hash_seq_term(&scan);
+
+ /*
+ * If the released connection was an orphan, just close it.
+ */
+ if (entry == NULL)
+ {
+ PQfinish(conn);
+ return;
+ }
+
+ /* If the caller was the last referer, unregister it from cache. */
+ entry->refs--;
+ elog(DEBUG3, "ref %d for %s", entry->refs, entry->name);
+ if (entry->refs == 0)
+ {
+ elog(DEBUG3, "closing connection \"%s\"", entry->name);
+ PQfinish(entry->conn);
+ entry->refs = 0;
+ entry->conn = NULL;
+ }
+}
+
+/*
+ * Clean the connection up via ResourceOwner.
+ */
+static void
+cleanup_connection(ResourceReleasePhase phase,
+ bool isCommit,
+ bool isTopLevel,
+ void *arg)
+{
+ ConnCacheEntry *entry = (ConnCacheEntry *) arg;
+
+ /* If the transaction was committed, don't close connections. */
+ if (isCommit)
+ return;
+
+ /*
+ * We clean the connection up on post-lock because foreign connections are
+ * backend-internal resource.
+ */
+ if (phase != RESOURCE_RELEASE_AFTER_LOCKS)
+ return;
+
+ /*
+ * We ignore cleanup for ResourceOwners other than transaction. At this
+ * point, such a ResourceOwner is only Portal.
+ */
+ if (CurrentResourceOwner != CurTransactionResourceOwner)
+ return;
+
+ /*
+ * We don't care whether we are in TopTransaction or Subtransaction.
+ * Anyway, we close the connection and reset the reference counter.
+ */
+ if (entry->conn != NULL)
+ {
+ elog(DEBUG3, "closing connection to %s", entry->name);
+ PQfinish(entry->conn);
+ entry->refs = 0;
+ entry->conn = NULL;
+ }
+ else
+ elog(DEBUG3, "connection to %s already closed", entry->name);
+}
+
+
diff --git a/contrib/postgresql_fdw/connection.h b/contrib/postgresql_fdw/connection.h
new file mode 100644
index 0000000000..1497d17724
--- /dev/null
+++ b/contrib/postgresql_fdw/connection.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * connectoin.h
+ * Connection management for postgresql_fdw
+ *
+ * Portions Copyright (c) 2010-2011, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/postgresql_fdw/connection.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef CONNECTION_H
+#define CONNECTION_H
+
+#include "foreign/foreign.h"
+#include "libpq-fe.h"
+
+/*
+ * Connection management
+ */
+PGconn *GetConnection(ForeignServer *server, UserMapping *user);
+void ReleaseConnection(PGconn *conn);
+
+#endif /* CONNECTION_H */
diff --git a/contrib/postgresql_fdw/deparse.c b/contrib/postgresql_fdw/deparse.c
new file mode 100644
index 0000000000..3303fde4aa
--- /dev/null
+++ b/contrib/postgresql_fdw/deparse.c
@@ -0,0 +1,364 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse.c
+ * Utility for deparsing parse-tree into SQL statement.
+ *
+ * Portions Copyright (c) 2010-2011, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/postgresql_fdw/deparse.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/pg_namespace.h"
+#include "foreign/foreign.h"
+#include "lib/stringinfo.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/relation.h"
+#include "optimizer/clauses.h"
+#include "optimizer/var.h"
+#include "parser/parsetree.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+
+#include "deparse.h"
+
+/* helper for deparsing a request into SQL statement */
+static bool is_foreign_qual(PlannerInfo *root, RelOptInfo *baserel, Expr *expr);
+static bool foreign_qual_walker(Node *node, void *context);
+
+/*
+ * Deparse a var into column name, result is quoted if necessary.
+ */
+static void
+deparse_var(PlannerInfo *root, StringInfo buf, Var *var)
+{
+ RangeTblEntry *rte;
+ char *attname;
+
+ if (var->varlevelsup != 0)
+ elog(ERROR, "unexpected varlevelsup %d in remote query",
+ var->varlevelsup);
+
+ if (var->varno < 1 || var->varno > list_length(root->parse->rtable))
+ elog(ERROR, "unexpected varno %d in remote query", var->varno);
+ rte = rt_fetch(var->varno, root->parse->rtable);
+
+ attname = get_rte_attribute_name(rte, var->varattno);
+ appendStringInfoString(buf, quote_identifier(attname));
+}
+
+typedef struct
+{
+ PlannerInfo *root;
+ RelOptInfo *foreignrel;
+} remotely_executable_cxt;
+
+/*
+ * return true if procid can be executed in foreign server.
+ */
+static bool
+is_proc_remotely_executable(Oid procid)
+{
+ /*
+ * assume that only built-in functions can be pushed down.
+ * TODO routine mapping should be used to determine whether the function
+ * can be pushed down.
+ */
+ if (get_func_namespace(procid) != PG_CATALOG_NAMESPACE)
+ return false;
+ /* we don't check volatility here, that's done once at the top-level */
+
+ return true;
+}
+
+/*
+ * return true if node can NOT be evaluatated in foreign server.
+ *
+ * An expression which consists of expressions below can be evaluated in
+ * the foreign server.
+ * - constant value
+ * - variable (foreign table column)
+ * - external parameter (parameter of prepared statement)
+ * - array
+ * - bool expression (AND/OR/NOT)
+ * - NULL test (IS [NOT] NULL)
+ * - operator
+ * - IMMUTABLE or STABLE only
+ * - It is required that the meaning of the operator be the same as the
+ * local server in the foreign server.
+ * - function
+ * - IMMUTABLE or STABLE only
+ * - It is required that the meaning of the operator be the same as the
+ * local server in the foreign server.
+ * - scalar array operator (ANY/ALL)
+ */
+static bool
+foreign_qual_walker(Node *node, void *context)
+{
+ if (node == NULL)
+ return false;
+
+ switch (nodeTag(node))
+ {
+ case T_Const:
+ case T_ArrayExpr:
+ case T_BoolExpr:
+ case T_NullTest:
+ case T_DistinctExpr:
+ case T_ScalarArrayOpExpr:
+ /*
+ * These type of nodes are known as safe to be pushed down.
+ * Of course the subtree of the node, if any, should be checked
+ * continuously.
+ */
+ break;
+ case T_Param:
+ /*
+ * External parameter can be pushed down.
+ * TODO: Pass internal parameters to the foreign server. It needs
+ * index renumbering.
+ */
+ {
+ if (((Param *) node)->paramkind != PARAM_EXTERN)
+ return true;
+ }
+ break;
+ case T_OpExpr:
+ {
+ OpExpr *op = (OpExpr *) node;
+
+ if (!is_proc_remotely_executable(op->opfuncid))
+ return true;
+ /* arguments are checked later via walker */
+ }
+ break;
+ case T_FuncExpr:
+ {
+ FuncExpr *fe = (FuncExpr *) node;
+
+ if (!is_proc_remotely_executable(fe->funcid))
+ return true;
+ /* arguments are checked later via walker */
+ }
+ break;
+ case T_Var:
+ {
+ /*
+ * Var can be pushed down if it is in the foreign table.
+ * XXX Var of other relation can be here?
+ */
+ Var *var = (Var *) node;
+ remotely_executable_cxt *r_context;
+
+ r_context = (remotely_executable_cxt *) context;
+ if (var->varno != r_context->foreignrel->relid ||
+ var->varlevelsup != 0)
+ return true;
+ }
+ break;
+ default:
+ /* Other expression can't be pushed down */
+ elog(DEBUG3, "node is too complex");
+ elog(DEBUG3, "%s", nodeToString(node));
+ return true;
+ }
+
+ return expression_tree_walker(node, foreign_qual_walker, context);
+}
+
+
+/*
+ * Check whether the ExprState node can be evaluated in foreign server.
+ *
+ * Actual check is implemented in foreign_qual_walker.
+ */
+static bool
+is_foreign_qual(PlannerInfo *root, RelOptInfo *baserel, Expr *expr)
+{
+ remotely_executable_cxt context;
+
+ context.root = root;
+ context.foreignrel = baserel;
+
+ /*
+ * Check that the expression consists of nodes which are known as safe to
+ * be pushed down.
+ */
+ if (foreign_qual_walker((Node *) expr, &context))
+ return false;
+
+ /* Check that the expression doesn't include any volatile function. */
+ if (contain_volatile_functions((Node *) expr))
+ return false;
+
+ return true;
+}
+
+/*
+ * Deparse query request into SQL statement.
+ *
+ * If an expression in PlanState.qual list satisfies is_foreign_qual(), the
+ * expression is:
+ * - deparsed into WHERE clause of remote SQL statement to evaluate that
+ * expression on remote side
+ * - removed from PlanState.qual list to avoid duplicate evaluation, on
+ * remote side and local side
+ */
+char *
+deparseSql(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel)
+{
+ AttrNumber attr;
+ List *attr_used = NIL;
+ List *context;
+ List *foreign_expr = NIL;
+ StringInfoData sql;
+ ForeignTable *table = GetForeignTable(foreigntableid);
+ ListCell *lc;
+ bool first;
+ char *nspname = NULL;
+ char *relname = NULL;
+
+ /* extract ForeignScan and RangeTblEntry */
+
+ /* prepare to deparse plan */
+ initStringInfo(&sql);
+
+ context = deparse_context_for("foreigntable", foreigntableid);
+
+ /*
+ * Determine which qual can be pushed down.
+ *
+ * The expressions which satisfy is_foreign_qual() are deparsed into WHERE
+ * clause of result SQL string, and they could be removed from qual of
+ * PlanState to avoid duplicate evaluation at ExecScan().
+ *
+ * We never change the qual in the Plan node which was made by PREPARE
+ * statement to make following EXECUTE statements work properly. The Plan
+ * node is used repeatedly to create PlanState for each EXECUTE statement.
+ *
+ * We do this before deparsing SELECT clause because attributes which
+ * aren't used in neither reltargetlist nor baserel->baserestrictinfo,
+ * quals evaluated on local, can be replaced with NULL in the SELECT
+ * clause.
+ */
+ if (baserel->baserestrictinfo)
+ {
+ List *local_qual = NIL;
+ ListCell *lc;
+
+ /*
+ * Divide qual of PlanState into two lists, one for local evaluation
+ * and one for foreign evaluation.
+ */
+ foreach (lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
+
+ if (is_foreign_qual(root, baserel, ri->clause))
+ {
+ /* XXX: deparse and add to sql here */
+ foreign_expr = lappend(foreign_expr, ri->clause);
+ }
+ else
+ local_qual = lappend(local_qual, ri);
+ }
+ /*
+ * XXX: If the remote side is not reliable enough, we can keep the qual
+ * in PlanState as is and evaluate them on local side too. If so, just
+ * omit replacement below.
+ */
+ baserel->baserestrictinfo = local_qual;
+
+ }
+
+ /* Collect used columns from restrict information and target list */
+ attr_used = list_union(attr_used, baserel->reltargetlist);
+ foreach (lc, baserel->baserestrictinfo)
+ {
+ List *l;
+ RestrictInfo *ri = lfirst(lc);
+
+ l = pull_var_clause((Node *) ri->clause, PVC_RECURSE_PLACEHOLDERS);
+ attr_used = list_union(attr_used, l);
+ }
+
+ /* deparse SELECT target list */
+ appendStringInfoString(&sql, "SELECT ");
+ first = true;
+ for (attr = 1; attr <= baserel->max_attr; attr++)
+ {
+ Var *var;
+
+ if (!first)
+ appendStringInfoString(&sql, ", ");
+ first = false;
+
+ /* Use "NULL" for unused columns. */
+ foreach (lc, attr_used)
+ {
+ var = lfirst(lc);
+ if (var->varattno == attr)
+ break;
+ var = NULL;
+ }
+
+ if (var != NULL)
+ deparse_var(root, &sql, var);
+ else
+ appendStringInfo(&sql, "NULL");
+ }
+
+ /*
+ * Deparse FROM
+ *
+ * If the foreign table has generic option "nspname" and/or "relname", use
+ * them in the foreign query. Otherwise, use local catalog names.
+ */
+ foreach(lc, table->options)
+ {
+ DefElem *opt = lfirst(lc);
+ if (strcmp(opt->defname, "nspname") == 0)
+ nspname = pstrdup(strVal(opt->arg));
+ else if (strcmp(opt->defname, "relname") == 0)
+ relname = pstrdup(strVal(opt->arg));
+ }
+ if (nspname == NULL)
+ nspname = get_namespace_name(get_rel_namespace(foreigntableid));
+ if (relname == NULL)
+ relname = get_rel_name(foreigntableid);
+ appendStringInfo(&sql, " FROM %s.%s",
+ quote_identifier(nspname),
+ quote_identifier(relname));
+
+ /*
+ * deparse WHERE if some quals can be pushed down
+ */
+ if (foreign_expr != NIL)
+ {
+ /*
+ * Deparse quals to be evaluated in the foreign server if any.
+ * TODO: modify deparse_expression() to deparse conditions which use
+ * internal parameters.
+ */
+ if (foreign_expr != NIL)
+ {
+ Node *node;
+ node = (Node *) make_ands_explicit(foreign_expr);
+ appendStringInfo(&sql, " WHERE %s",
+ deparse_expression(node, context, false, false));
+ /*
+ * The contents of the list MUST NOT be free-ed because they are
+ * referenced from Plan.qual list.
+ */
+ list_free(foreign_expr);
+ }
+ }
+
+ elog(DEBUG1, "deparsed SQL is \"%s\"", sql.data);
+
+ return sql.data;
+}
+
diff --git a/contrib/postgresql_fdw/deparse.h b/contrib/postgresql_fdw/deparse.h
new file mode 100644
index 0000000000..bf1b018243
--- /dev/null
+++ b/contrib/postgresql_fdw/deparse.h
@@ -0,0 +1,18 @@
+/*
+ * deparse.h
+ *
+ * contrib/postgresql_fdw/deparse.h
+ * Copyright (c) 2011, PostgreSQL Global Development Group
+ * ALL RIGHTS RESERVED;
+ *
+ */
+
+#ifndef DEPARSE_H
+#define DEPARSE_H
+
+/*
+ * deparse.c: deparsing query-tree into SQL statement
+ */
+char *deparseSql(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel);
+
+#endif /* DEPARSE_H */
diff --git a/contrib/postgresql_fdw/expected/postgresql_fdw.out b/contrib/postgresql_fdw/expected/postgresql_fdw.out
new file mode 100644
index 0000000000..dea1ca1a40
--- /dev/null
+++ b/contrib/postgresql_fdw/expected/postgresql_fdw.out
@@ -0,0 +1,154 @@
+SET DATESTYLE = 'Postgres, MDY';
+-- =============================================================================
+-- Prepare section
+-- =============================================================================
+CREATE EXTENSION postgresql_fdw;
+-- connect database for regression test
+\c contrib_regression
+-- define fdw-related objects
+CREATE SERVER loopback1 FOREIGN DATA WRAPPER postgresql_fdw
+ OPTIONS (dbname 'contrib_regression');
+CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgresql_fdw
+ OPTIONS (dbname 'contrib_regression');
+CREATE USER MAPPING FOR PUBLIC SERVER loopback1;
+CREATE USER MAPPING FOR PUBLIC SERVER loopback2 OPTIONS (user 'invalid');
+CREATE TABLE t1(
+ c1 integer,
+ c2 text,
+ c3 date
+);
+COPY t1 FROM stdin;
+CREATE TABLE t2(
+ c1 integer,
+ c2 text,
+ c3 date
+);
+COPY t2 FROM stdin;
+CREATE FOREIGN TABLE ft1 (
+ c1 integer,
+ c2 text,
+ c3 date
+) SERVER loopback1 OPTIONS (relname 't1');
+CREATE FOREIGN TABLE ft2 (
+ c1 integer,
+ c2 text,
+ c3 date
+) SERVER loopback2 OPTIONS (relname 'invalid');
+-- simple query and connection caching
+SELECT ft1.* FROM ft1 ORDER BY c1;
+ c1 | c2 | c3
+----+-----+------------
+ 1 | foo | 01-01-1970
+ 2 | bar | 01-02-1970
+ 3 | buz | 01-03-1970
+(3 rows)
+
+SELECT * FROM ft2 ORDER BY c1; -- ERROR
+ERROR: could not connect to server "loopback2"
+DETAIL: FATAL: role "invalid" does not exist
+
+ALTER USER MAPPING FOR PUBLIC SERVER loopback2 OPTIONS (DROP user);
+SELECT * FROM ft2 ORDER BY c1; -- ERROR
+ERROR: could not execute foreign query
+DETAIL: ERROR: relation "public.invalid" does not exist
+LINE 1: SELECT c1, c2, c3 FROM public.invalid
+ ^
+
+HINT: SELECT c1, c2, c3 FROM public.invalid
+ALTER FOREIGN TABLE ft2 OPTIONS (SET relname 't2');
+SELECT * FROM ft2 ORDER BY c1;
+ c1 | c2 | c3
+----+-----+------------
+ 1 | foo | 01-01-1970
+ 12 | bar | 01-02-1970
+ 13 | buz | 01-03-1970
+(3 rows)
+
+-- subquery
+SELECT c1 FROM t1 WHERE c1 = (SELECT c1 FROM ft1 ORDER BY c1 LIMIT 1);
+ c1
+----
+ 1
+(1 row)
+
+SELECT c1 FROM ft1 WHERE c1 = (SELECT c1 FROM t1 ORDER BY c1 LIMIT 1);
+ c1
+----
+ 1
+(1 row)
+
+-- query with projection
+SELECT c1 FROM ft1 ORDER BY c1;
+ c1
+----
+ 1
+ 2
+ 3
+(3 rows)
+
+-- join two foreign tables
+SELECT * FROM ft1 JOIN ft2 ON (ft1.c1 = ft2.c1) ORDER BY ft1.c1;
+ c1 | c2 | c3 | c1 | c2 | c3
+----+-----+------------+----+-----+------------
+ 1 | foo | 01-01-1970 | 1 | foo | 01-01-1970
+(1 row)
+
+-- join itself
+SELECT * FROM ft1 t1 JOIN ft1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1;
+ c1 | c2 | c3 | c1 | c2 | c3
+----+-----+------------+----+-----+------------
+ 1 | foo | 01-01-1970 | 1 | foo | 01-01-1970
+ 2 | bar | 01-02-1970 | 2 | bar | 01-02-1970
+ 3 | buz | 01-03-1970 | 3 | buz | 01-03-1970
+(3 rows)
+
+-- outer join
+SELECT * FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY 1,2,3,4,5,6;
+ c1 | c2 | c3 | c1 | c2 | c3
+----+-----+------------+----+-----+------------
+ 1 | foo | 01-01-1970 | 1 | foo | 01-01-1970
+ 2 | bar | 01-02-1970 | | |
+ 3 | buz | 01-03-1970 | | |
+(3 rows)
+
+-- WHERE clause push-down
+SELECT * FROM ft1 WHERE c1 = 1 AND c2 = lower('FOO') AND c3 < now() and c3 < clock_timestamp();
+ c1 | c2 | c3
+----+-----+------------
+ 1 | foo | 01-01-1970
+(1 row)
+
+EXPLAIN (COSTS FALSE) SELECT * FROM ft1 WHERE c1 = 1 AND c2 = lower('FOO') AND c3 < now() and c3 < clock_timestamp();
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------
+ Foreign Scan on ft1
+ Filter: (c3 < clock_timestamp())
+ Remote SQL: SELECT c1, c2, c3 FROM public.t1 WHERE ((c3 < now()) AND (c1 = 1) AND (c2 = 'foo'::text))
+(3 rows)
+
+-- prepared statement
+PREPARE st(int) AS SELECT * FROM ft1 WHERE c1 > $1 ORDER BY c1;
+EXECUTE st(1);
+ c1 | c2 | c3
+----+-----+------------
+ 2 | bar | 01-02-1970
+ 3 | buz | 01-03-1970
+(2 rows)
+
+EXECUTE st(2);
+ c1 | c2 | c3
+----+-----+------------
+ 3 | buz | 01-03-1970
+(1 row)
+
+DEALLOCATE st;
+-- clean up
+DROP TABLE t1 CASCADE;
+DROP EXTENSION postgresql_fdw CASCADE;
+NOTICE: drop cascades to 6 other objects
+DETAIL: drop cascades to server loopback1
+drop cascades to user mapping for public
+drop cascades to foreign table ft1
+drop cascades to server loopback2
+drop cascades to user mapping for public
+drop cascades to foreign table ft2
diff --git a/contrib/postgresql_fdw/postgresql_fdw--1.0.sql b/contrib/postgresql_fdw/postgresql_fdw--1.0.sql
new file mode 100644
index 0000000000..37facd424e
--- /dev/null
+++ b/contrib/postgresql_fdw/postgresql_fdw--1.0.sql
@@ -0,0 +1,11 @@
+/* contrib/postgresql_fdw/postgresql_fdw--1.0.sql */
+
+CREATE OR REPLACE FUNCTION postgresql_fdw_handler ()
+RETURNS fdw_handler
+AS 'MODULE_PATHNAME','postgresql_fdw_handler'
+LANGUAGE C STRICT;
+
+CREATE FOREIGN DATA WRAPPER postgresql_fdw
+VALIDATOR postgresql_fdw_validator
+HANDLER postgresql_fdw_handler;
+
diff --git a/contrib/postgresql_fdw/postgresql_fdw.c b/contrib/postgresql_fdw/postgresql_fdw.c
new file mode 100644
index 0000000000..9fe7348f02
--- /dev/null
+++ b/contrib/postgresql_fdw/postgresql_fdw.c
@@ -0,0 +1,366 @@
+/*-------------------------------------------------------------------------
+ *
+ * postgresql_fdw.c
+ * foreign-data wrapper for PostgreSQL
+ *
+ * Portions Copyright (c) 2010-2011, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/postgresql_fdw/postgresql_fdw.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "commands/explain.h"
+#include "foreign/fdwapi.h"
+#include "foreign/foreign.h"
+#include "funcapi.h"
+#include "libpq-fe.h"
+#include "miscadmin.h"
+#include "optimizer/plancat.h"
+#include "parser/parsetree.h"
+#include "utils/lsyscache.h"
+
+#include "connection.h"
+#include "deparse.h"
+
+PG_MODULE_MAGIC;
+
+extern Datum postgresql_fdw_handler(PG_FUNCTION_ARGS);
+
+/*
+ * FDW routines
+ */
+static FdwPlan *pgPlanForeignScan(Oid foreigntableid, PlannerInfo *root,
+ RelOptInfo *baserel);
+static void pgExplainForeignScan(ForeignScanState *node,
+ struct ExplainState *es);
+static void pgBeginForeignScan(ForeignScanState *node, int eflags);
+static TupleTableSlot *pgIterateForeignScan(ForeignScanState *node);
+static void pgReScanForeignScan(ForeignScanState *node);
+static void pgEndForeignScan(ForeignScanState *node);
+
+static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, Cost *total_cost);
+static void storeResult(TupleTableSlot *slot, PGresult *res, int rowno);
+
+/*
+ * PostgreSQL specific portion of a foreign query request
+ */
+typedef struct pgFdwExecutionState
+{
+ PGconn *conn; /* connection used for the scan */
+ PGresult *res; /* result of the scan, held until the scan ends */
+ int nextrow; /* row index to be returned in next fetch */
+} pgFdwExecutionState;
+
+
+/*
+ * return foreign-data wrapper handler object to execute foreign-data wrapper
+ * routines.
+ */
+PG_FUNCTION_INFO_V1(postgresql_fdw_handler);
+Datum
+postgresql_fdw_handler(PG_FUNCTION_ARGS)
+{
+ FdwRoutine *fdwroutine = makeNode(FdwRoutine);
+
+ fdwroutine->PlanForeignScan = pgPlanForeignScan;
+ fdwroutine->ExplainForeignScan = pgExplainForeignScan;
+ fdwroutine->BeginForeignScan = pgBeginForeignScan;
+ fdwroutine->IterateForeignScan = pgIterateForeignScan;
+ fdwroutine->ReScanForeignScan = pgReScanForeignScan;
+ fdwroutine->EndForeignScan = pgEndForeignScan;
+
+ PG_RETURN_POINTER(fdwroutine);
+}
+
+/*
+ * pgPlanForeignScan
+ * Create FdwPlan for the scan
+ */
+static FdwPlan *
+pgPlanForeignScan(Oid foreigntableid, PlannerInfo *root, RelOptInfo *baserel)
+{
+ FdwPlan *fdwplan;
+ char *sql;
+
+ fdwplan = makeNode(FdwPlan);
+
+ sql = deparseSql(foreigntableid, root, baserel);
+
+ estimate_costs(root, baserel, &fdwplan->startup_cost,
+ &fdwplan->total_cost);
+
+ fdwplan->fdw_private = lappend(NIL, makeString(sql));
+
+ return fdwplan;
+}
+
+/*
+ * pgExplainForeignScan
+ * Produce extra output for EXPLAIN
+ */
+static void
+pgExplainForeignScan(ForeignScanState *node, struct ExplainState *es)
+{
+ FdwPlan *fdwplan;
+ char *sql;
+
+ fdwplan = ((ForeignScan *) node->ss.ps.plan)->fdwplan;
+ sql = strVal(list_nth(fdwplan->fdw_private, 0));
+ ExplainPropertyText("Remote SQL", sql, es);
+}
+
+/*
+ * Initiate actual scan on a foreign table.
+ */
+static void
+pgBeginForeignScan(ForeignScanState *node, int eflags)
+{
+ pgFdwExecutionState *festate;
+
+ Oid relid;
+ ForeignTable *table;
+ ForeignServer *server;
+ UserMapping *user;
+
+ FdwPlan *fdwplan;
+ const char *sql;
+ PGconn *conn;
+ PGresult *res;
+ ParamListInfo params = node->ss.ps.state->es_param_list_info;
+ int numParams = params ? params->numParams : 0;
+ Oid *types = NULL;
+ const char **values = NULL;
+
+ elog(DEBUG3, "%s() called", __FUNCTION__);
+
+ /*
+ * Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL.
+ */
+ if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
+ return;
+
+ festate = palloc(sizeof(pgFdwExecutionState));
+
+ /* Get connection to external PostgreSQL server. */
+ relid = RelationGetRelid(node->ss.ss_currentRelation);
+ table = GetForeignTable(relid);
+ server = GetForeignServer(table->serverid);
+ user = GetUserMapping(GetOuterUserId(), table->serverid);
+ conn = GetConnection(server, user);
+ festate->conn = conn;
+
+ /* construct parameter array in text format */
+ /* TODO: omit unused parameter */
+ if (numParams > 0)
+ {
+ int i;
+
+ types = palloc0(sizeof(Oid) * numParams);
+ values = palloc0(sizeof(char *) * numParams);
+ for (i = 0; i < numParams; i++)
+ {
+ types[i] = params->params[i].ptype;
+ if (params->params[i].isnull)
+ values[i] = NULL;
+ else
+ {
+ Oid out_func_oid;
+ bool isvarlena;
+ FmgrInfo func;
+
+ /* TODO: send parameters in binary format rather than text */
+ getTypeOutputInfo(types[i], &out_func_oid, &isvarlena);
+ fmgr_info(out_func_oid, &func);
+ values[i] =
+ OutputFunctionCall(&func, params->params[i].value);
+ }
+ }
+ }
+
+ /*
+ * Execute query with the parameters.
+ * TODO: support internal parameters(PARAM_EXTERN)
+ * TODO: support cursor mode for huge result sets.
+ */
+ fdwplan = ((ForeignScan *) node->ss.ps.plan)->fdwplan;
+ sql = strVal(list_nth(fdwplan->fdw_private, 0));
+ res = PQexecParams(conn, sql, numParams, types, values, NULL, NULL, 0);
+ if (numParams > 0)
+ {
+ int i;
+ pfree(types);
+ for (i = 0; i < numParams; i++)
+ pfree((char *) values[i]);
+ pfree(values);
+ }
+
+ /*
+ * If the query has failed, reporting details is enough here.
+ * Connections which are used by this query (including other scans) will
+ * be cleaned up by the foreign connection manager.
+ */
+ if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ char *msg;
+
+ msg = pstrdup(PQerrorMessage(conn));
+ PQclear(res);
+ ereport(ERROR,
+ (errmsg("could not execute foreign query"),
+ errdetail("%s", msg),
+ errhint("%s", sql)));
+ }
+
+ festate->res = res;
+ festate->nextrow = 0;
+
+ node->fdw_state = (void *) festate;
+}
+
+/*
+ * return tuples one by one.
+ * - execute SQL statement which was deparsed in pgBeginForeignScan()
+ *
+ * The all of result are fetched at once when pgIterateForeignScan() is called
+ * first after pgBeginForeignScan() or pgReScanForeignScan().
+ * pgIterateForeignScan() moves the next tuple from tupstore to TupleTableSlot
+ * in ScanState.
+ */
+static TupleTableSlot *
+pgIterateForeignScan(ForeignScanState *node)
+{
+ TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+ pgFdwExecutionState *festate = (pgFdwExecutionState *) node->fdw_state;
+
+ elog(DEBUG3, "%s() called", __FUNCTION__);
+
+ if (festate->nextrow == PQntuples(festate->res))
+ ExecClearTuple(slot);
+ else
+ storeResult(slot, festate->res, festate->nextrow++);
+
+ return slot;
+}
+
+/*
+ * Restart this scan by reseting next fetch location.
+ */
+static void
+pgReScanForeignScan(ForeignScanState *node)
+{
+ ((pgFdwExecutionState *) node->fdw_state)->nextrow = 0;
+}
+
+/*
+ * Release resources used for this foreign scan.
+ */
+static void
+pgEndForeignScan(ForeignScanState *node)
+{
+ pgFdwExecutionState *festate = (pgFdwExecutionState *) node->fdw_state;
+
+ if (festate == NULL)
+ return;
+
+ PQclear(festate->res);
+ ReleaseConnection(festate->conn);
+ pfree(festate);
+}
+
+/*
+ * Store a PGresult into tuplestore.
+ */
+static void
+storeResult(TupleTableSlot *slot, PGresult *res, int rowno)
+{
+ int i;
+ int nfields;
+ int attnum; /* number of non-dropped columns */
+ char **values;
+ AttInMetadata *attinmeta;
+ Form_pg_attribute *attrs;
+ TupleDesc tupdesc = slot->tts_tupleDescriptor;
+
+ nfields = PQnfields(res);
+ attrs = tupdesc->attrs;
+
+ /* count non-dropped columns */
+ for (attnum = 0, i = 0; i < tupdesc->natts; i++)
+ if (!attrs[i]->attisdropped)
+ attnum++;
+
+ /* check result and tuple descriptor have the same number of columns */
+ if (attnum > 0 && attnum != nfields)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("remote query result rowtype does not match "
+ "the specified FROM clause rowtype"),
+ errdetail("expected %d, actual %d", attnum, nfields)));
+
+ /* buffer should include dropped columns */
+ values = palloc(sizeof(char *) * tupdesc->natts);
+
+ /* put all tuples into the tuplestore */
+ attinmeta = TupleDescGetAttInMetadata(tupdesc);
+ {
+ int j;
+ HeapTuple tuple;
+
+ for (i = 0, j = 0; i < tupdesc->natts; i++)
+ {
+ /* skip dropped columns. */
+ if (attrs[i]->attisdropped)
+ {
+ values[i] = NULL;
+ continue;
+ }
+
+ if (PQgetisnull(res, rowno, j))
+ values[i] = NULL;
+ else
+ values[i] = PQgetvalue(res, rowno, j);
+ j++;
+ }
+
+ /*
+ * Build the tuple and put it into the slot.
+ * We don't have to free the tuple explicitly because it's been
+ * allocated in the per-tuple context.
+ */
+ tuple = BuildTupleFromCStrings(attinmeta, values);
+ ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ }
+
+ pfree(values);
+}
+
+
+/*
+ * Estimate costs of scanning on a foreign table.
+ *
+ * baserel->baserestrictinfo can be used to examine quals on the relation.
+ */
+static void
+estimate_costs(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, Cost *total_cost)
+{
+ RangeTblEntry *rte;
+ double connection_cost = 0.0;
+ double transfer_cost = 0.0;
+
+ elog(DEBUG3, "%s() called", __FUNCTION__);
+
+ /* get avarage width estimation */
+ rte = planner_rt_fetch(baserel->relid, root);
+ baserel->width = get_relation_data_width(rte->relid, baserel->attr_widths);
+
+ /* XXX arbitrary guesses */
+ connection_cost = 10.0;
+ transfer_cost = 1.0;
+ *startup_cost += connection_cost;
+ *total_cost += connection_cost;
+ *total_cost += transfer_cost * baserel->width * baserel->tuples;
+}
+
diff --git a/contrib/postgresql_fdw/postgresql_fdw.control b/contrib/postgresql_fdw/postgresql_fdw.control
new file mode 100644
index 0000000000..ef78aafefd
--- /dev/null
+++ b/contrib/postgresql_fdw/postgresql_fdw.control
@@ -0,0 +1,5 @@
+# postgresql_fdw extension
+comment = 'Foreign-data wrapepr for external PostgreSQL server'
+default_version = '1.0'
+module_pathname = '$libdir/postgresql_fdw'
+relocatable = true
diff --git a/contrib/postgresql_fdw/sql/postgresql_fdw.sql b/contrib/postgresql_fdw/sql/postgresql_fdw.sql
new file mode 100644
index 0000000000..b75f09d846
--- /dev/null
+++ b/contrib/postgresql_fdw/sql/postgresql_fdw.sql
@@ -0,0 +1,88 @@
+SET DATESTYLE = 'Postgres, MDY';
+
+-- =============================================================================
+-- Prepare section
+-- =============================================================================
+CREATE EXTENSION postgresql_fdw;
+
+-- connect database for regression test
+\c contrib_regression
+
+-- define fdw-related objects
+CREATE SERVER loopback1 FOREIGN DATA WRAPPER postgresql_fdw
+ OPTIONS (dbname 'contrib_regression');
+CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgresql_fdw
+ OPTIONS (dbname 'contrib_regression');
+
+CREATE USER MAPPING FOR PUBLIC SERVER loopback1;
+CREATE USER MAPPING FOR PUBLIC SERVER loopback2 OPTIONS (user 'invalid');
+
+CREATE TABLE t1(
+ c1 integer,
+ c2 text,
+ c3 date
+);
+
+COPY t1 FROM stdin;
+1 foo 1970-01-01
+2 bar 1970-01-02
+3 buz 1970-01-03
+\.
+
+CREATE TABLE t2(
+ c1 integer,
+ c2 text,
+ c3 date
+);
+
+COPY t2 FROM stdin;
+1 foo 1970-01-01
+12 bar 1970-01-02
+13 buz 1970-01-03
+\.
+
+CREATE FOREIGN TABLE ft1 (
+ c1 integer,
+ c2 text,
+ c3 date
+) SERVER loopback1 OPTIONS (relname 't1');
+
+CREATE FOREIGN TABLE ft2 (
+ c1 integer,
+ c2 text,
+ c3 date
+) SERVER loopback2 OPTIONS (relname 'invalid');
+
+-- simple query and connection caching
+SELECT ft1.* FROM ft1 ORDER BY c1;
+SELECT * FROM ft2 ORDER BY c1; -- ERROR
+ALTER USER MAPPING FOR PUBLIC SERVER loopback2 OPTIONS (DROP user);
+SELECT * FROM ft2 ORDER BY c1; -- ERROR
+ALTER FOREIGN TABLE ft2 OPTIONS (SET relname 't2');
+SELECT * FROM ft2 ORDER BY c1;
+
+-- subquery
+SELECT c1 FROM t1 WHERE c1 = (SELECT c1 FROM ft1 ORDER BY c1 LIMIT 1);
+SELECT c1 FROM ft1 WHERE c1 = (SELECT c1 FROM t1 ORDER BY c1 LIMIT 1);
+
+-- query with projection
+SELECT c1 FROM ft1 ORDER BY c1;
+
+-- join two foreign tables
+SELECT * FROM ft1 JOIN ft2 ON (ft1.c1 = ft2.c1) ORDER BY ft1.c1;
+-- join itself
+SELECT * FROM ft1 t1 JOIN ft1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1;
+-- outer join
+SELECT * FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY 1,2,3,4,5,6;
+-- WHERE clause push-down
+SELECT * FROM ft1 WHERE c1 = 1 AND c2 = lower('FOO') AND c3 < now() and c3 < clock_timestamp();
+EXPLAIN (COSTS FALSE) SELECT * FROM ft1 WHERE c1 = 1 AND c2 = lower('FOO') AND c3 < now() and c3 < clock_timestamp();
+-- prepared statement
+PREPARE st(int) AS SELECT * FROM ft1 WHERE c1 > $1 ORDER BY c1;
+EXECUTE st(1);
+EXECUTE st(2);
+DEALLOCATE st;
+
+-- clean up
+DROP TABLE t1 CASCADE;
+DROP EXTENSION postgresql_fdw CASCADE;
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index adf09ca872..b60436cba1 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -123,6 +123,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
&pgtestfsync;
&pgtrgm;
&pgupgrade;
+ &postgresql-fdw;
&seg;
&sepgsql;
&contrib-spi;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index ed39e0b661..6a734647a5 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -129,6 +129,7 @@
<!ENTITY pgtestfsync SYSTEM "pgtestfsync.sgml">
<!ENTITY pgtrgm SYSTEM "pgtrgm.sgml">
<!ENTITY pgupgrade SYSTEM "pgupgrade.sgml">
+<!ENTITY postgresql-fdw SYSTEM "postgresql-fdw.sgml">
<!ENTITY seg SYSTEM "seg.sgml">
<!ENTITY contrib-spi SYSTEM "contrib-spi.sgml">
<!ENTITY sepgsql SYSTEM "sepgsql.sgml">
diff --git a/doc/src/sgml/postgresql-fdw.sgml b/doc/src/sgml/postgresql-fdw.sgml
new file mode 100644
index 0000000000..a368acbdae
--- /dev/null
+++ b/doc/src/sgml/postgresql-fdw.sgml
@@ -0,0 +1,346 @@
+<!-- doc/src/sgml/postgresql_fdw.sgml -->
+
+<sect1 id="postgresql-fdw">
+ <title>postgresql_fdw</title>
+
+ <indexterm zone="postgresql-fdw">
+ <primary>postgresql_fdw</primary>
+ </indexterm>
+
+ <para>
+ The <filename>postgresql_fdw</> module provides foreign-data wrapper
+ handler function <function>postgresql_fdw_handler</function> which can be
+ used to access external <productname>PostgreSQL</> server via plain SQL.
+ </para>
+
+ <sect2>
+ <title>Options</title>
+
+ <sect3>
+ <title>Connection options</title>
+ <para>
+ <filename>postgresql_fdw</> retrieves connection information from options
+ of foreign server and user mapping.
+ <literal>user</literal> and <literal>password</literal> can be
+ specified only for user mapping, and other options can be
+ specified for only foreign server. The <filename> postgresql_fdw</>
+ accepts all libpq connection options.
+ Connection information which was not specified on FDW objects is determined
+ with libpq's rule.
+ See <xref linkend="sql-createforeigndatawrapper">,
+ <xref linkend="sql-createserver">,
+ <xref linkend="sql-createusermapping"> for details of FDW object
+ definition.
+ See <xref linkend="libpq-connect"> for details of libpq connection
+ information.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Object name options</title>
+ <para>
+ <filename>postgresql_fdw</> accepts <literal>nspname</literal>
+ and <literal>relname</literal> options which can be used to specify
+ object name used in the remote query. The <literal>nspname</literal>
+ is used as schema name, and <literal>relname</literal> is used as
+ relation name.
+ These options are retrieved from foreign table's definition.
+ If these options are omitted , local catalog names are used.
+ See <xref linkend="sql-createforeigntable"> for details of defining foreign
+ table.
+ </para>
+ </sect3>
+
+ </sect2>
+
+ <sect2>
+ <title>Examples</title>
+
+ <para>
+It is assumed that tables and data are prepared on the remote side.
+<programlisting>
+CREATE USER pgfdw_remote WITH PASSWORD 'secret';
+SET ROLE pgfdw_remote;
+
+-- definition of external data
+CREATE TABLE films (
+ code char(5) CONSTRAINT firstkey PRIMARY KEY,
+ title varchar(40) NOT NULL,
+ did integer NOT NULL,
+ date_prod date,
+ kind varchar(10),
+ len interval hour to minute
+);
+
+INSERT INTO films VALUES
+ ('UA502', 'Bananas', 105, '1971-07-13', 'Comedy', '82 minutes'),
+ ('T_601', 'Yojimbo', 106, '1961-06-16', 'Drama', NULL),
+ ('B6717', 'Tampopo', 110, '1985-02-10', 'Comedy', NULL),
+ ('HG120', 'The Dinner Game', 140, DEFAULT, 'Comedy', NULL);
+</programlisting>
+ </para>
+
+ <para>
+ On the local side, you need to build and install <filename>postgresql_fdw</>
+ binary first. Then you can create foreign-data wrapper
+ <filename>postgresql_fdw</> by executing
+ <xref linkend="sql-createextension">.
+ After creating foreign-data wrapper, you need to create user
+<programlisting>
+-- this creates foreign-data wrapper postgresql_fdw automatically
+CREATE EXTENSION postgresql_fdw;
+
+-- store connection information per server
+CREATE SERVER myserver FOREIGN DATA WRAPPER postgresql_fdw
+ OPTIONS (host 'foo', dbname 'foodb', port '5432');
+
+-- map local user to remote user
+CREATE USER MAPPING FOR pgfdw_local SERVER myserver
+ OPTIONS (user 'pgfdw_remote', password 'secret');
+
+-- define form of remote data as foreign table
+CREATE FOREIGN TABLE external_table_films (
+ code char(5) NOT NULL,
+ title varchar(40) NOT NULL,
+ did integer NOT NULL,
+ date_prod date,
+ kind varchar(10),
+ len interval hour to minute
+) SERVER myserver OPTIONS (relname 'films');
+
+-- now you can retrieve data from remote PostgreSQL server
+SELECT * FROM external_table_films;
+ code | title | did | date_prod | kind | len
+-------+-----------------+-----+------------+--------+----------
+ UA502 | Bananas | 105 | 1971-07-13 | Comedy | 01:22:00
+ T_601 | Yojimbo | 106 | 1961-06-16 | Drama |
+ B6717 | Tampopo | 110 | 1985-02-10 | Comedy |
+ HG120 | The Dinner Game | 140 | | Comedy |
+(4 rows)
+</programlisting>
+ </para>
+
+ </sect2>
+
+ <sect2>
+ <title>Function</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <function>postgresql_fdw_handler() returns fdw_handler</function>
+ </term>
+
+ <listitem>
+ <para>
+ <function>postgresql_fdw_handler</function> is a foreign-data wrapper
+ handler function which returns foreign-data wrapper handler for
+ <productname>PostgreSQL</> in type of <type>fdw_handler</type>.
+ Since <type>fdw_handler</type> is a pseudo type, <function>
+ postgresql_fdw_handler</function> can't be called from a SQL statement.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ </sect2>
+
+ <sect2>
+ <title>Notes</title>
+
+ <para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ The <filename>postgresql_fdw</> connects to a remote <productname>
+ PostgreSQL</> server when a scan on the server is requested first
+ time in the local query. The connection is used by all of remote
+ queries which are executed on same remote <productname>PostgreSQL</>
+ server. If the local query uses multiple foreign <productname>
+ PostgreSQL</> servers, connections are established for each server
+ (not for each foreign table) and all of them will be closed at the
+ end of the query. This also means that connection pooling is not
+ implemented in <filename>postgresql_fdw</>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The <filename>postgresql_fdw</> never emit transaction command such
+ as <command>BEGIN</>, <command>ROLLBACK</> and <command>COMMIT</>.
+ Thus, all SQL statements are executed in each transaction when
+ <varname>autocommit</> was set to 'on'.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The <filename>postgresql_fdw</> retrieves all of the result tuples
+ at once via libpq when the query was executed. Note that huge result
+ set causes huge memory consumption. The memory for the result set
+ will be freed at the end of the each query.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <xref linkend="sql-explain"> shows a local plan for the query.
+ "Remote SQL" of ForeignScan node is the query which will be send to the
+ remote server.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The <filename>postgresql_fdw</> pushes some part of <command>WHERE</>
+ clause down to the remote server, only if the evaluating the part
+ of clause doesn't break the consistency of the query. If a clause
+ consist of elements below, the clause will be pushed down.
+ </para>
+ <table id="postgresql-fdw-push-downable">
+ <title>push-down-able elements</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Element</entry>
+ <entry>Note</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>Constant value and column reference</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Array of push-down-able type</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Parameter of <command>EXECUTE</command></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Bool expression such as <literal>A AND B</literal> or
+ <literal>A OR B</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Non-volatile operator</entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry><command>DISTINCT</command>> operator, such as
+ <literal>A IS DISTINCT FROM B</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Scalar array operator, such as <literal>ALL(...)</literal> and
+ <literal>ANY(...)</literal></entry>
+ <entry></entry>
+ </row>
+ <row>
+ <entry>Non-volatile function call</entry>
+ <entry></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ <para>
+ "Remote query" in the result of <xref linkend="sql-explain"> shows
+ whether each condition in the <command>WHERE</> clause of the local
+ query will be send to remote side or not.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ All conditions in <command>HAVING</command> clause are evaluated on the
+ local side because aggregate push-down has not been implemented yet.
+ <command>UNION</command> clause, <command>ORDER BY</command> clause and
+ <command>LIMIT</command> clause are also evaluated on the local side.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Functions which are used in <command>SELECT</command> clause are executed
+ on local side.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ If you want to use a set returning function like
+ <function>generate_series</> as data source, you need to define a view on
+ the remote side and specify its name as relname option of the foreign
+ table. In this way, sequences and large objects on the remote side can be
+ handled via <filename>postgresql_fdw</> if the operation doesn't need any
+ parameter.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Note that the difference between local and remote could cause unexpected
+ results.
+ <itemizedlist>
+ <listitem>
+ <para>
+ The second part of local <varname>datestyle</>, which specifies
+ year/month/day ordering, should fit remote <varname>datestyle</>.
+ For example, if the remote <varname>datestyle</> contains
+ <literal>German</> as the first part, local <varname>datestyle</>
+ should contain <literal>dmy</> as the second part.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ If settings of <varname>timezone</> are different between local and
+ remote, note that <type>timestamp without time zone</> values will be
+ treated as local timezone.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Locale parameters affect to various appearance such as messages and
+ currency format.
+ </para>
+ <para>
+ If you use <function>to_char</> function to format currency value in
+ <command>SELECT</> statement, local locale setting will be used
+ because <function>to_char</>, a stable function, will be evaluated on
+ the local side. If you want to use remote locale for formatting, you
+ would need a view which use <function>to_char</> on remote side.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <filename>postgresql_fdw</> uses libpq version-3 to retrieve external
+ data. PostgreSQL 7.4 or higher are available as remote server.
+ Anyway, please ensure that functions/operators have same meaning on both
+ side because <filename>postgresql_fdw</> generates SQL statement based
+ on definitions of functions and operators on the local side.
+ </para>
+ <para>
+ If a function or an operator which is used in local query doesn't exist,
+ remote query will fail and cause error.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ pg_dump dumps only definition of foreign tables, and doesn't dump
+ external data. If you want to dump external data on local side, use
+ <command>COPY</> for each foreign table.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Foreign tables can't have OID system column. If the external table was
+ defined with <literal>WITH OIDS</>, add <literal>oid</> column to the
+ foreign table. Note that <command>SELECT * FROM foreign_table</> will
+ return oid always in such case.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect2>
+
+</sect1>
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 4a7b2c30cf..54d33eedb3 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -402,10 +402,11 @@ pg_options_to_table(PG_FUNCTION_ARGS)
/*
* Describes the valid options for postgresql FDW, server, and user mapping.
*/
-struct ConnectionOption
+struct FdwOption
{
- const char *optname;
+ const char *optname; /* name of the option */
Oid optcontext; /* Oid of catalog in which option may appear */
+ bool is_conninfo; /* T if the option is a libpq conninfo option */
};
/*
@@ -413,22 +414,26 @@ struct ConnectionOption
*
* 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}
+static struct FdwOption postgresql_fdw_options[] = {
+ /* libpq connection options */
+ {"authtype", ForeignServerRelationId, true},
+ {"service", ForeignServerRelationId, true},
+ {"user", UserMappingRelationId, true},
+ {"password", UserMappingRelationId, true},
+ {"connect_timeout", ForeignServerRelationId, true},
+ {"dbname", ForeignServerRelationId, true},
+ {"host", ForeignServerRelationId, true},
+ {"hostaddr", ForeignServerRelationId, true},
+ {"port", ForeignServerRelationId, true},
+ {"tty", ForeignServerRelationId, true},
+ {"options", ForeignServerRelationId, true},
+ {"requiressl", ForeignServerRelationId, true},
+ {"sslmode", ForeignServerRelationId, true},
+ {"gsslib", ForeignServerRelationId, true},
+ /* catalog options */
+ {"nspname", ForeignTableRelationId, false},
+ {"relname", ForeignTableRelationId, false},
+ {NULL, InvalidOid, false}
};
@@ -437,12 +442,28 @@ static struct ConnectionOption libpq_conninfo_options[] = {
* context is the Oid of the catalog the option came from, or 0 if we
* don't care.
*/
+bool
+is_conninfo_option(const char *option)
+{
+ struct FdwOption *opt;
+
+ for (opt = postgresql_fdw_options; opt->optname; opt++)
+ if (strcmp(opt->optname, option) == 0)
+ return true;
+ return false;
+}
+
+/*
+ * Check if the provided option is one of postgresql_fdw 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)
+is_postgresql_fdw_option(const char *option, Oid context)
{
- struct ConnectionOption *opt;
+ struct FdwOption *opt;
- for (opt = libpq_conninfo_options; opt->optname; opt++)
+ for (opt = postgresql_fdw_options; opt->optname; opt++)
if (context == opt->optcontext && strcmp(opt->optname, option) == 0)
return true;
return false;
@@ -469,9 +490,9 @@ postgresql_fdw_validator(PG_FUNCTION_ARGS)
{
DefElem *def = lfirst(cell);
- if (!is_conninfo_option(def->defname, catalog))
+ if (!is_postgresql_fdw_option(def->defname, catalog))
{
- struct ConnectionOption *opt;
+ struct FdwOption *opt;
StringInfoData buf;
/*
@@ -479,7 +500,7 @@ postgresql_fdw_validator(PG_FUNCTION_ARGS)
* with list of valid options for the object.
*/
initStringInfo(&buf);
- for (opt = libpq_conninfo_options; opt->optname; opt++)
+ for (opt = postgresql_fdw_options; opt->optname; opt++)
if (catalog == opt->optcontext)
appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
opt->optname);
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 2c436aef80..094ac58d79 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -75,6 +75,7 @@ extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
bool missing_ok);
extern ForeignTable *GetForeignTable(Oid relid);
+bool is_conninfo_option(const char *option);
extern Oid get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok);
extern Oid get_foreign_server_oid(const char *servername, bool missing_ok);