pgcrypto    \
         pgrowlocks  \
         pgstattuple \
 +       postgres_fdw    \
         seg     \
         spi     \
         tablefunc   \
      --- /dev/null
   +# Generated subdirectories
 +/log/
 +/results/
 +/tmp_check/
      --- /dev/null
   +# 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
      --- /dev/null
   +/*-------------------------------------------------------------------------
 + *
 + * connection.c
 + *       Connection management functions for postgres_fdw
 + *
 + * Portions Copyright (c) 2012-2013, PostgreSQL Global Development Group
 + *
 + * IDENTIFICATION
 + *       contrib/postgres_fdw/connection.c
 + *
 + *-------------------------------------------------------------------------
 + */
 +#include "postgres.h"
 +
 +#include "postgres_fdw.h"
 +
 +#include "access/xact.h"
 +#include "mb/pg_wchar.h"
 +#include "miscadmin.h"
 +#include "utils/hsearch.h"
 +#include "utils/memutils.h"
 +
 +
 +/*
 + * Connection cache hash table entry
 + *
 + * The lookup key in this hash table is the foreign server OID plus the user
 + * mapping OID.  (We use just one connection per user per foreign server,
 + * so that we can ensure all scans use the same snapshot during a query.)
 + *
 + * The "conn" pointer can be NULL if we don't currently have a live connection.
 + * When we do have a connection, xact_depth tracks the current depth of
 + * transactions and subtransactions open on the remote side.  We need to issue
 + * commands at the same nesting depth on the remote as we're executing at
 + * ourselves, so that rolling back a subtransaction will kill the right
 + * queries and not the wrong ones.
 + */
 +typedef struct ConnCacheKey
 +{
 +   Oid         serverid;       /* OID of foreign server */
 +   Oid         userid;         /* OID of local user whose mapping we use */
 +} ConnCacheKey;
 +
 +typedef struct ConnCacheEntry
 +{
 +   ConnCacheKey key;           /* hash key (must be first) */
 +   PGconn     *conn;           /* connection to foreign server, or NULL */
 +   int         xact_depth;     /* 0 = no xact open, 1 = main xact open, 2 =
 +                                * one level of subxact open, etc */
 +} ConnCacheEntry;
 +
 +/*
 + * Connection cache (initialized on first use)
 + */
 +static HTAB *ConnectionHash = NULL;
 +
 +/* for assigning cursor numbers */
 +static unsigned int cursor_number = 0;
 +
 +/* tracks whether any work is needed in callback functions */
 +static bool xact_got_connection = false;
 +
 +/* prototypes of private functions */
 +static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
 +static void check_conn_params(const char **keywords, const char **values);
 +static void begin_remote_xact(ConnCacheEntry *entry);
 +static void pgfdw_xact_callback(XactEvent event, void *arg);
 +static void pgfdw_subxact_callback(SubXactEvent event,
 +                      SubTransactionId mySubid,
 +                      SubTransactionId parentSubid,
 +                      void *arg);
 +
 +
 +/*
 + * Get a PGconn which can be used to execute queries on the remote PostgreSQL
 + * server with the user's authorization.  A new connection is established
 + * if we don't already have a suitable one, and a transaction is opened at
 + * the right subtransaction nesting depth if we didn't do that already.
 + *
 + * XXX Note that caching connections theoretically requires a mechanism to
 + * detect change of FDW objects to invalidate already established connections.
 + * We could manage that by watching for invalidation events on the relevant
 + * syscaches.  For the moment, though, it's not clear that this would really
 + * be useful and not mere pedantry.  We could not flush any active connections
 + * mid-transaction anyway.
 + */
 +PGconn *
 +GetConnection(ForeignServer *server, UserMapping *user)
 +{
 +   bool        found;
 +   ConnCacheEntry *entry;
 +   ConnCacheKey key;
 +
 +   /* First time through, initialize connection cache hashtable */
 +   if (ConnectionHash == NULL)
 +   {
 +       HASHCTL     ctl;
 +
 +       MemSet(&ctl, 0, sizeof(ctl));
 +       ctl.keysize = sizeof(ConnCacheKey);
 +       ctl.entrysize = sizeof(ConnCacheEntry);
 +       ctl.hash = tag_hash;
 +       /* allocate ConnectionHash in the cache context */
 +       ctl.hcxt = CacheMemoryContext;
 +       ConnectionHash = hash_create("postgres_fdw connections", 8,
 +                                    &ctl,
 +                                  HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
 +
 +       /*
 +        * Register some callback functions that manage connection cleanup.
 +        * This should be done just once in each backend.
 +        */
 +       RegisterXactCallback(pgfdw_xact_callback, NULL);
 +       RegisterSubXactCallback(pgfdw_subxact_callback, NULL);
 +   }
 +
 +   /* Set flag that we did GetConnection during the current transaction */
 +   xact_got_connection = true;
 +
 +   /* Create hash key for the entry.  Assume no pad bytes in key struct */
 +   key.serverid = server->serverid;
 +   key.userid = user->userid;
 +
 +   /*
 +    * Find or create cached entry for requested connection.
 +    */
 +   entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found);
 +   if (!found)
 +   {
 +       /* initialize new hashtable entry (key is already filled in) */
 +       entry->conn = NULL;
 +       entry->xact_depth = 0;
 +   }
 +
 +   /*
 +    * We don't check the health of cached connection here, because it would
 +    * require some overhead.  Broken connection will be detected when the
 +    * connection is actually used.
 +    */
 +
 +   /*
 +    * If cache entry doesn't have a connection, we have to establish a new
 +    * connection.  (If connect_pg_server throws an error, the cache entry
 +    * will be left in a valid empty state.)
 +    */
 +   if (entry->conn == NULL)
 +   {
 +       entry->xact_depth = 0;  /* just to be sure */
 +       entry->conn = connect_pg_server(server, user);
 +       elog(DEBUG3, "new postgres_fdw connection %p for server \"%s\"",
 +            entry->conn, server->servername);
 +   }
 +
 +   /*
 +    * Start a new transaction or subtransaction if needed.
 +    */
 +   begin_remote_xact(entry);
 +
 +   return entry->conn;
 +}
 +
 +/*
 + * Connect to remote server using specified server and user mapping properties.
 + */
 +static PGconn *
 +connect_pg_server(ForeignServer *server, UserMapping *user)
 +{
 +   PGconn     *volatile conn = NULL;
 +
 +   /*
 +    * Use PG_TRY block to ensure closing connection on error.
 +    */
 +   PG_TRY();
 +   {
 +       const char **keywords;
 +       const char **values;
 +       int         n;
 +
 +       /*
 +        * Construct connection params from generic options of ForeignServer
 +        * and UserMapping.  (Some of them might not be libpq options, in
 +        * which case we'll just waste a few array slots.)  Add 3 extra slots
 +        * for fallback_application_name, client_encoding, end marker.
 +        */
 +       n = list_length(server->options) + list_length(user->options) + 3;
 +       keywords = (const char **) palloc(n * sizeof(char *));
 +       values = (const char **) palloc(n * sizeof(char *));
 +
 +       n = 0;
 +       n += ExtractConnectionOptions(server->options,
 +                                     keywords + n, values + n);
 +       n += ExtractConnectionOptions(user->options,
 +                                     keywords + n, values + n);
 +
 +       /* Use "postgres_fdw" as fallback_application_name. */
 +       keywords[n] = "fallback_application_name";
 +       values[n] = "postgres_fdw";
 +       n++;
 +
 +       /* Set client_encoding so that libpq can convert encoding properly. */
 +       keywords[n] = "client_encoding";
 +       values[n] = GetDatabaseEncodingName();
 +       n++;
 +
 +       keywords[n] = values[n] = NULL;
 +
 +       /* verify connection parameters and make connection */
 +       check_conn_params(keywords, values);
 +
 +       conn = PQconnectdbParams(keywords, values, false);
 +       if (!conn || PQstatus(conn) != CONNECTION_OK)
 +       {
 +           char       *connmessage;
 +           int         msglen;
 +
 +           /* libpq typically appends a newline, strip that */
 +           connmessage = pstrdup(PQerrorMessage(conn));
 +           msglen = strlen(connmessage);
 +           if (msglen > 0 && connmessage[msglen - 1] == '\n')
 +               connmessage[msglen - 1] = '\0';
 +           ereport(ERROR,
 +              (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),
 +               errmsg("could not connect to server \"%s\"",
 +                      server->servername),
 +               errdetail_internal("%s", connmessage)));
 +       }
 +
 +       /*
 +        * Check that non-superuser has used password to establish connection;
 +        * otherwise, he's piggybacking on the postgres server's user
 +        * identity. See also dblink_security_check() in contrib/dblink.
 +        */
 +       if (!superuser() && !PQconnectionUsedPassword(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.")));
 +
 +       pfree(keywords);
 +       pfree(values);
 +   }
 +   PG_CATCH();
 +   {
 +       /* Release PGconn data structure if we managed to create one */
 +       if (conn)
 +           PQfinish(conn);
 +       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. (See also dblink_connstr_check in
 + * contrib/dblink.)
 + */
 +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 user mapping.")));
 +}
 +
 +/*
 + * Start remote transaction or subtransaction, if needed.
 + *
 + * Note that we always use at least REPEATABLE READ in the remote session.
 + * This is so that, if a query initiates multiple scans of the same or
 + * different foreign tables, we will get snapshot-consistent results from
 + * those scans.  A disadvantage is that we can't provide sane emulation of
 + * READ COMMITTED behavior --- it would be nice if we had some other way to
 + * control which remote queries share a snapshot.
 + */
 +static void
 +begin_remote_xact(ConnCacheEntry *entry)
 +{
 +   int         curlevel = GetCurrentTransactionNestLevel();
 +   PGresult   *res;
 +
 +   /* Start main transaction if we haven't yet */
 +   if (entry->xact_depth <= 0)
 +   {
 +       const char *sql;
 +
 +       elog(DEBUG3, "starting remote transaction on connection %p",
 +            entry->conn);
 +
 +       if (XactIsoLevel == XACT_SERIALIZABLE)
 +           sql = "START TRANSACTION ISOLATION LEVEL SERIALIZABLE";
 +       else
 +           sql = "START TRANSACTION ISOLATION LEVEL REPEATABLE READ";
 +       res = PQexec(entry->conn, sql);
 +       if (PQresultStatus(res) != PGRES_COMMAND_OK)
 +           pgfdw_report_error(ERROR, res, true, sql);
 +       PQclear(res);
 +       entry->xact_depth = 1;
 +   }
 +
 +   /*
 +    * If we're in a subtransaction, stack up savepoints to match our level.
 +    * This ensures we can rollback just the desired effects when a
 +    * subtransaction aborts.
 +    */
 +   while (entry->xact_depth < curlevel)
 +   {
 +       char        sql[64];
 +
 +       snprintf(sql, sizeof(sql), "SAVEPOINT s%d", entry->xact_depth + 1);
 +       res = PQexec(entry->conn, sql);
 +       if (PQresultStatus(res) != PGRES_COMMAND_OK)
 +           pgfdw_report_error(ERROR, res, true, sql);
 +       PQclear(res);
 +       entry->xact_depth++;
 +   }
 +}
 +
 +/*
 + * Release connection reference count created by calling GetConnection.
 + */
 +void
 +ReleaseConnection(PGconn *conn)
 +{
 +   /*
 +    * Currently, we don't actually track connection references because all
 +    * cleanup is managed on a transaction or subtransaction basis instead. So
 +    * there's nothing to do here.
 +    */
 +}
 +
 +/*
 + * Assign a "unique" number for a cursor.
 + *
 + * These really only need to be unique per connection within a transaction.
 + * For the moment we ignore the per-connection point and assign them across
 + * all connections in the transaction, but we ask for the connection to be
 + * supplied in case we want to refine that.
 + *
 + * Note that even if wraparound happens in a very long transaction, actual
 + * collisions are highly improbable; just be sure to use %u not %d to print.
 + */
 +unsigned int
 +GetCursorNumber(PGconn *conn)
 +{
 +   return ++cursor_number;
 +}
 +
 +/*
 + * Report an error we got from the remote server.
 + *
 + * elevel: error level to use (typically ERROR, but might be less)
 + * res: PGresult containing the error
 + * clear: if true, PQclear the result (otherwise caller will handle it)
 + * sql: NULL, or text of remote command we tried to execute
 + */
 +void
 +pgfdw_report_error(int elevel, PGresult *res, bool clear, const char *sql)
 +{
 +   /* If requested, PGresult must be released before leaving this function. */
 +   PG_TRY();
 +   {
 +       char       *diag_sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE);
 +       char       *message_primary = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY);
 +       char       *message_detail = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL);
 +       char       *message_hint = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT);
 +       char       *message_context = PQresultErrorField(res, PG_DIAG_CONTEXT);
 +       int         sqlstate;
 +
 +       if (diag_sqlstate)
 +           sqlstate = MAKE_SQLSTATE(diag_sqlstate[0],
 +                                    diag_sqlstate[1],
 +                                    diag_sqlstate[2],
 +                                    diag_sqlstate[3],
 +                                    diag_sqlstate[4]);
 +       else
 +           sqlstate = ERRCODE_CONNECTION_FAILURE;
 +
 +       ereport(elevel,
 +               (errcode(sqlstate),
 +                message_primary ? errmsg_internal("%s", message_primary) :
 +                errmsg("unknown error"),
 +              message_detail ? errdetail_internal("%s", message_detail) : 0,
 +                message_hint ? errhint("%s", message_hint) : 0,
 +                message_context ? errcontext("%s", message_context) : 0,
 +                sql ? errcontext("Remote SQL command: %s", sql) : 0));
 +   }
 +   PG_CATCH();
 +   {
 +       if (clear)
 +           PQclear(res);
 +       PG_RE_THROW();
 +   }
 +   PG_END_TRY();
 +   if (clear)
 +       PQclear(res);
 +}
 +
 +/*
 + * pgfdw_xact_callback --- cleanup at main-transaction end.
 + */
 +static void
 +pgfdw_xact_callback(XactEvent event, void *arg)
 +{
 +   HASH_SEQ_STATUS scan;
 +   ConnCacheEntry *entry;
 +
 +   /* Quick exit if no connections were touched in this transaction. */
 +   if (!xact_got_connection)
 +       return;
 +
 +   /*
 +    * Scan all connection cache entries to find open remote transactions, and
 +    * close them.
 +    */
 +   hash_seq_init(&scan, ConnectionHash);
 +   while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
 +   {
 +       PGresult   *res;
 +
 +       /* We only care about connections with open remote transactions */
 +       if (entry->conn == NULL || entry->xact_depth == 0)
 +           continue;
 +
 +       elog(DEBUG3, "closing remote transaction on connection %p",
 +            entry->conn);
 +
 +       switch (event)
 +       {
 +           case XACT_EVENT_PRE_COMMIT:
 +               /* Commit all remote transactions during pre-commit */
 +               res = PQexec(entry->conn, "COMMIT TRANSACTION");
 +               if (PQresultStatus(res) != PGRES_COMMAND_OK)
 +                   pgfdw_report_error(ERROR, res, true, "COMMIT TRANSACTION");
 +               PQclear(res);
 +               break;
 +           case XACT_EVENT_PRE_PREPARE:
 +
 +               /*
 +                * We disallow remote transactions that modified anything,
 +                * since it's not really reasonable to hold them open until
 +                * the prepared transaction is committed.  For the moment,
 +                * throw error unconditionally; later we might allow read-only
 +                * cases.  Note that the error will cause us to come right
 +                * back here with event == XACT_EVENT_ABORT, so we'll clean up
 +                * the connection state at that point.
 +                */
 +               ereport(ERROR,
 +                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 +                        errmsg("cannot prepare a transaction that modified remote tables")));
 +               break;
 +           case XACT_EVENT_COMMIT:
 +           case XACT_EVENT_PREPARE:
 +               /* Should not get here -- pre-commit should have handled it */
 +               elog(ERROR, "missed cleaning up connection during pre-commit");
 +               break;
 +           case XACT_EVENT_ABORT:
 +               /* If we're aborting, abort all remote transactions too */
 +               res = PQexec(entry->conn, "ABORT TRANSACTION");
 +               /* Note: can't throw ERROR, it would be infinite loop */
 +               if (PQresultStatus(res) != PGRES_COMMAND_OK)
 +                   pgfdw_report_error(WARNING, res, true,
 +                                      "ABORT TRANSACTION");
 +               else
 +                   PQclear(res);
 +               break;
 +       }
 +
 +       /* Reset state to show we're out of a transaction */
 +       entry->xact_depth = 0;
 +
 +       /*
 +        * If the connection isn't in a good idle state, discard it to
 +        * recover. Next GetConnection will open a new connection.
 +        */
 +       if (PQstatus(entry->conn) != CONNECTION_OK ||
 +           PQtransactionStatus(entry->conn) != PQTRANS_IDLE)
 +       {
 +           elog(DEBUG3, "discarding connection %p", entry->conn);
 +           PQfinish(entry->conn);
 +           entry->conn = NULL;
 +       }
 +   }
 +
 +   /*
 +    * Regardless of the event type, we can now mark ourselves as out of the
 +    * transaction.  (Note: if we are here during PRE_COMMIT or PRE_PREPARE,
 +    * this saves a useless scan of the hashtable during COMMIT or PREPARE.)
 +    */
 +   xact_got_connection = false;
 +
 +   /* Also reset cursor numbering for next transaction */
 +   cursor_number = 0;
 +}
 +
 +/*
 + * pgfdw_subxact_callback --- cleanup at subtransaction end.
 + */
 +static void
 +pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
 +                      SubTransactionId parentSubid, void *arg)
 +{
 +   HASH_SEQ_STATUS scan;
 +   ConnCacheEntry *entry;
 +   int         curlevel;
 +
 +   /* Nothing to do at subxact start, nor after commit. */
 +   if (!(event == SUBXACT_EVENT_PRE_COMMIT_SUB ||
 +         event == SUBXACT_EVENT_ABORT_SUB))
 +       return;
 +
 +   /* Quick exit if no connections were touched in this transaction. */
 +   if (!xact_got_connection)
 +       return;
 +
 +   /*
 +    * Scan all connection cache entries to find open remote subtransactions
 +    * of the current level, and close them.
 +    */
 +   curlevel = GetCurrentTransactionNestLevel();
 +   hash_seq_init(&scan, ConnectionHash);
 +   while ((entry = (ConnCacheEntry *) hash_seq_search(&scan)))
 +   {
 +       PGresult   *res;
 +       char        sql[100];
 +
 +       /*
 +        * We only care about connections with open remote subtransactions of
 +        * the current level.
 +        */
 +       if (entry->conn == NULL || entry->xact_depth < curlevel)
 +           continue;
 +
 +       if (entry->xact_depth > curlevel)
 +           elog(ERROR, "missed cleaning up remote subtransaction at level %d",
 +                entry->xact_depth);
 +
 +       if (event == SUBXACT_EVENT_PRE_COMMIT_SUB)
 +       {
 +           /* Commit all remote subtransactions during pre-commit */
 +           snprintf(sql, sizeof(sql), "RELEASE SAVEPOINT s%d", curlevel);
 +           res = PQexec(entry->conn, sql);
 +           if (PQresultStatus(res) != PGRES_COMMAND_OK)
 +               pgfdw_report_error(ERROR, res, true, sql);
 +           PQclear(res);
 +       }
 +       else
 +       {
 +           /* Rollback all remote subtransactions during abort */
 +           snprintf(sql, sizeof(sql),
 +                    "ROLLBACK TO SAVEPOINT s%d; RELEASE SAVEPOINT s%d",
 +                    curlevel, curlevel);
 +           res = PQexec(entry->conn, sql);
 +           if (PQresultStatus(res) != PGRES_COMMAND_OK)
 +               pgfdw_report_error(WARNING, res, true, sql);
 +           else
 +               PQclear(res);
 +       }
 +
 +       /* OK, we're outta that level of subtransaction */
 +       entry->xact_depth--;
 +   }
 +}
      --- /dev/null
   +/*-------------------------------------------------------------------------
 + *
 + * deparse.c
 + *       Query deparser for postgres_fdw
 + *
 + * This file includes functions that examine query WHERE clauses to see
 + * whether they're safe to send to the remote server for execution, as
 + * well as functions to construct the query text to be sent.  The latter
 + * functionality is annoyingly duplicative of ruleutils.c, but there are
 + * enough special considerations that it seems best to keep this separate.
 + * One saving grace is that we only need deparse logic for node types that
 + * we consider safe to send.
 + *
 + * Portions Copyright (c) 2012-2013, PostgreSQL Global Development Group
 + *
 + * IDENTIFICATION
 + *       contrib/postgres_fdw/deparse.c
 + *
 + *-------------------------------------------------------------------------
 + */
 +#include "postgres.h"
 +
 +#include "postgres_fdw.h"
 +
 +#include "access/htup_details.h"
 +#include "access/sysattr.h"
 +#include "access/transam.h"
 +#include "catalog/pg_operator.h"
 +#include "catalog/pg_proc.h"
 +#include "catalog/pg_type.h"
 +#include "commands/defrem.h"
 +#include "nodes/nodeFuncs.h"
 +#include "optimizer/clauses.h"
 +#include "optimizer/var.h"
 +#include "parser/parsetree.h"
 +#include "utils/builtins.h"
 +#include "utils/lsyscache.h"
 +#include "utils/syscache.h"
 +
 +
 +/*
 + * Context for foreign_expr_walker's search of an expression tree.
 + */
 +typedef struct foreign_expr_cxt
 +{
 +   /* Input values */
 +   PlannerInfo *root;
 +   RelOptInfo *foreignrel;
 +   /* Result values */
 +   List       *param_numbers;  /* Param IDs of PARAM_EXTERN Params */
 +} foreign_expr_cxt;
 +
 +/*
 + * Functions to determine whether an expression can be evaluated safely on
 + * remote server.
 + */
 +static bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel,
 +               Expr *expr, List **param_numbers);
 +static bool foreign_expr_walker(Node *node, foreign_expr_cxt *context);
 +static bool is_builtin(Oid procid);
 +
 +/*
 + * Functions to construct string representation of a node tree.
 + */
 +static void deparseColumnRef(StringInfo buf, int varno, int varattno,
 +                PlannerInfo *root);
 +static void deparseRelation(StringInfo buf, Oid relid);
 +static void deparseStringLiteral(StringInfo buf, const char *val);
 +static void deparseExpr(StringInfo buf, Expr *expr, PlannerInfo *root);
 +static void deparseVar(StringInfo buf, Var *node, PlannerInfo *root);
 +static void deparseConst(StringInfo buf, Const *node, PlannerInfo *root);
 +static void deparseParam(StringInfo buf, Param *node, PlannerInfo *root);
 +static void deparseArrayRef(StringInfo buf, ArrayRef *node, PlannerInfo *root);
 +static void deparseFuncExpr(StringInfo buf, FuncExpr *node, PlannerInfo *root);
 +static void deparseOpExpr(StringInfo buf, OpExpr *node, PlannerInfo *root);
 +static void deparseDistinctExpr(StringInfo buf, DistinctExpr *node,
 +                   PlannerInfo *root);
 +static void deparseScalarArrayOpExpr(StringInfo buf, ScalarArrayOpExpr *node,
 +                        PlannerInfo *root);
 +static void deparseRelabelType(StringInfo buf, RelabelType *node,
 +                  PlannerInfo *root);
 +static void deparseBoolExpr(StringInfo buf, BoolExpr *node, PlannerInfo *root);
 +static void deparseNullTest(StringInfo buf, NullTest *node, PlannerInfo *root);
 +static void deparseArrayExpr(StringInfo buf, ArrayExpr *node,
 +                PlannerInfo *root);
 +
 +
 +/*
 + * Examine each restriction clause in baserel's baserestrictinfo list,
 + * and classify them into three groups, which are returned as three lists:
 + * - remote_conds contains expressions that can be evaluated remotely,
 + *   and contain no PARAM_EXTERN Params
 + * - param_conds contains expressions that can be evaluated remotely,
 + *   but contain one or more PARAM_EXTERN Params
 + * - local_conds contains all expressions that can't be evaluated remotely
 + *
 + * In addition, the fourth output parameter param_numbers receives an integer
 + * list of the param IDs of the PARAM_EXTERN Params used in param_conds.
 + *
 + * The reason for segregating param_conds is mainly that it's difficult to
 + * use such conditions in remote EXPLAIN.  We could do it, but unless the
 + * planner has been given representative values for all the Params, we'd
 + * have to guess at representative values to use in EXPLAIN EXECUTE.
 + * So for now we don't include them when doing remote EXPLAIN.
 + */
 +void
 +classifyConditions(PlannerInfo *root,
 +                  RelOptInfo *baserel,
 +                  List **remote_conds,
 +                  List **param_conds,
 +                  List **local_conds,
 +                  List **param_numbers)
 +{
 +   ListCell   *lc;
 +
 +   *remote_conds = NIL;
 +   *param_conds = NIL;
 +   *local_conds = NIL;
 +   *param_numbers = NIL;
 +
 +   foreach(lc, baserel->baserestrictinfo)
 +   {
 +       RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
 +       List       *cur_param_numbers;
 +
 +       if (is_foreign_expr(root, baserel, ri->clause, &cur_param_numbers))
 +       {
 +           if (cur_param_numbers == NIL)
 +               *remote_conds = lappend(*remote_conds, ri);
 +           else
 +           {
 +               *param_conds = lappend(*param_conds, ri);
 +               /* Use list_concat_unique_int to get rid of duplicates */
 +               *param_numbers = list_concat_unique_int(*param_numbers,
 +                                                       cur_param_numbers);
 +           }
 +       }
 +       else
 +           *local_conds = lappend(*local_conds, ri);
 +   }
 +}
 +
 +/*
 + * Returns true if given expr is safe to evaluate on the foreign server.
 + *
 + * If result is true, we also return a list of param IDs of PARAM_EXTERN
 + * Params appearing in the expr into *param_numbers.
 + */
 +static bool
 +is_foreign_expr(PlannerInfo *root,
 +               RelOptInfo *baserel,
 +               Expr *expr,
 +               List **param_numbers)
 +{
 +   foreign_expr_cxt context;
 +
 +   *param_numbers = NIL;       /* default result */
 +
 +   /*
 +    * Check that the expression consists of nodes that are safe to execute
 +    * remotely.
 +    */
 +   context.root = root;
 +   context.foreignrel = baserel;
 +   context.param_numbers = NIL;
 +   if (foreign_expr_walker((Node *) expr, &context))
 +       return false;
 +
 +   /*
 +    * An expression which includes any mutable functions can't be sent over
 +    * because its result is not stable.  For example, sending now() remote
 +    * side could cause confusion from clock offsets.  Future versions might
 +    * be able to make this choice with more granularity.  (We check this last
 +    * because it requires a lot of expensive catalog lookups.)
 +    */
 +   if (contain_mutable_functions((Node *) expr))
 +       return false;
 +
 +   /*
 +    * OK, so return list of param IDs too.
 +    */
 +   *param_numbers = context.param_numbers;
 +
 +   return true;
 +}
 +
 +/*
 + * Return true if expression includes any node that is not safe to execute
 + * remotely.  (We use this convention because expression_tree_walker is
 + * designed to abort the tree walk as soon as a TRUE result is detected.)
 + */
 +static bool
 +foreign_expr_walker(Node *node, foreign_expr_cxt *context)
 +{
 +   bool        check_type = true;
 +
 +   if (node == NULL)
 +       return false;
 +
 +   switch (nodeTag(node))
 +   {
 +       case T_Var:
 +           {
 +               /*
 +                * Var can be used if it is in the foreign table (we shouldn't
 +                * really see anything else in baserestrict clauses, but let's
 +                * check anyway).
 +                */
 +               Var        *var = (Var *) node;
 +
 +               if (var->varno != context->foreignrel->relid ||
 +                   var->varlevelsup != 0)
 +                   return true;
 +           }
 +           break;
 +       case T_Const:
 +           /* OK */
 +           break;
 +       case T_Param:
 +           {
 +               Param      *p = (Param *) node;
 +
 +               /*
 +                * Only external parameters can be sent to remote.  (XXX This
 +                * needs to be improved, but at the point where this code
 +                * runs, we should only see PARAM_EXTERN Params anyway.)
 +                */
 +               if (p->paramkind != PARAM_EXTERN)
 +                   return true;
 +
 +               /*
 +                * Report IDs of PARAM_EXTERN Params.  We don't bother to
 +                * eliminate duplicate list elements here; classifyConditions
 +                * will do that.
 +                */
 +               context->param_numbers = lappend_int(context->param_numbers,
 +                                                    p->paramid);
 +           }
 +           break;
 +       case T_ArrayRef:
 +           {
 +               ArrayRef   *ar = (ArrayRef *) node;;
 +
 +               /* Assignment should not be in restrictions. */
 +               if (ar->refassgnexpr != NULL)
 +                   return true;
 +           }
 +           break;
 +       case T_FuncExpr:
 +           {
 +               /*
 +                * If function used by the expression is not built-in, it
 +                * can't be sent to remote because it might have incompatible
 +                * semantics on remote side.
 +                */
 +               FuncExpr   *fe = (FuncExpr *) node;
 +
 +               if (!is_builtin(fe->funcid))
 +                   return true;
 +           }
 +           break;
 +       case T_OpExpr:
 +       case T_DistinctExpr:    /* struct-equivalent to OpExpr */
 +           {
 +               /*
 +                * Similarly, only built-in operators can be sent to remote.
 +                * (If the operator is, surely its underlying function is
 +                * too.)
 +                */
 +               OpExpr     *oe = (OpExpr *) node;
 +
 +               if (!is_builtin(oe->opno))
 +                   return true;
 +           }
 +           break;
 +       case T_ScalarArrayOpExpr:
 +           {
 +               /*
 +                * Again, only built-in operators can be sent to remote.
 +                */
 +               ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node;
 +
 +               if (!is_builtin(oe->opno))
 +                   return true;
 +           }
 +           break;
 +       case T_RelabelType:
 +       case T_BoolExpr:
 +       case T_NullTest:
 +       case T_ArrayExpr:
 +           /* OK */
 +           break;
 +       case T_List:
 +
 +           /*
 +            * We need only fall through to let expression_tree_walker scan
 +            * the list elements --- but don't apply exprType() to the list.
 +            */
 +           check_type = false;
 +           break;
 +       default:
 +
 +           /*
 +            * If it's anything else, assume it's unsafe.  This list can be
 +            * expanded later, but don't forget to add deparse support below.
 +            */
 +           return true;
 +   }
 +
 +   /*
 +    * If result type of given expression is not built-in, it can't be sent to
 +    * remote because it might have incompatible semantics on remote side.
 +    */
 +   if (check_type && !is_builtin(exprType(node)))
 +       return true;
 +
 +   /* Recurse to examine sub-nodes */
 +   return expression_tree_walker(node, foreign_expr_walker, context);
 +}
 +
 +/*
 + * Return true if given object is one of PostgreSQL's built-in objects.
 + *
 + * XXX there is a problem with this, which is that the set of built-in
 + * objects expands over time.  Something that is built-in to us might not
 + * be known to the remote server, if it's of an older version.  But keeping
 + * track of that would be a huge exercise.
 + */
 +static bool
 +is_builtin(Oid oid)
 +{
 +   return (oid < FirstNormalObjectId);
 +}
 +
 +
 +/*
 + * Construct a simple SELECT statement that retrieves interesting columns
 + * of the specified foreign table, and append it to "buf". The output
 + * contains just "SELECT ... FROM tablename".
 + *
 + * "Interesting" columns are those appearing in the rel's targetlist or
 + * in local_conds (conditions which can't be executed remotely).
 + */
 +void
 +deparseSimpleSql(StringInfo buf,
 +                PlannerInfo *root,
 +                RelOptInfo *baserel,
 +                List *local_conds)
 +{
 +   RangeTblEntry *rte = root->simple_rte_array[baserel->relid];
 +   Bitmapset  *attrs_used = NULL;
 +   bool        first;
 +   AttrNumber  attr;
 +   ListCell   *lc;
 +
 +   /* Collect all the attributes needed for joins or final output. */
 +   pull_varattnos((Node *) baserel->reltargetlist, baserel->relid,
 +                  &attrs_used);
 +
 +   /* Add all the attributes used by local_conds. */
 +   foreach(lc, local_conds)
 +   {
 +       RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
 +
 +       pull_varattnos((Node *) rinfo->clause, baserel->relid,
 +                      &attrs_used);
 +   }
 +
 +   /*
 +    * Construct SELECT list
 +    *
 +    * We list attributes in order of the foreign table's columns, but replace
 +    * any attributes that need not be fetched with NULL constants. (We can't
 +    * just omit such attributes, or we'll lose track of which columns are
 +    * which at runtime.)  Note however that any dropped columns are ignored.
 +    */
 +   appendStringInfo(buf, "SELECT ");
 +   first = true;
 +   for (attr = 1; attr <= baserel->max_attr; attr++)
 +   {
 +       /* Ignore dropped attributes. */
 +       if (get_rte_attribute_is_dropped(rte, attr))
 +           continue;
 +
 +       if (!first)
 +           appendStringInfo(buf, ", ");
 +       first = false;
 +
 +       if (bms_is_member(attr - FirstLowInvalidHeapAttributeNumber,
 +                         attrs_used))
 +           deparseColumnRef(buf, baserel->relid, attr, root);
 +       else
 +           appendStringInfo(buf, "NULL");
 +   }
 +
 +   /* Don't generate bad syntax if no undropped columns */
 +   if (first)
 +       appendStringInfo(buf, "NULL");
 +
 +   /*
 +    * Construct FROM clause
 +    */
 +   appendStringInfo(buf, " FROM ");
 +   deparseRelation(buf, rte->relid);
 +}
 +
 +/*
 + * Deparse WHERE clauses in given list of RestrictInfos and append them to buf.
 + *
 + * If no WHERE clause already exists in the buffer, is_first should be true.
 + */
 +void
 +appendWhereClause(StringInfo buf,
 +                 bool is_first,
 +                 List *exprs,
 +                 PlannerInfo *root)
 +{
 +   ListCell   *lc;
 +
 +   foreach(lc, exprs)
 +   {
 +       RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
 +
 +       /* Connect expressions with "AND" and parenthesize each condition. */
 +       if (is_first)
 +           appendStringInfo(buf, " WHERE ");
 +       else
 +           appendStringInfo(buf, " AND ");
 +
 +       appendStringInfoChar(buf, '(');
 +       deparseExpr(buf, ri->clause, root);
 +       appendStringInfoChar(buf, ')');
 +
 +       is_first = false;
 +   }
 +}
 +
 +/*
 + * Construct SELECT statement to acquire sample rows of given relation.
 + *
 + * Note: command is appended to whatever might be in buf already.
 + */
 +void
 +deparseAnalyzeSql(StringInfo buf, Relation rel)
 +{
 +   Oid         relid = RelationGetRelid(rel);
 +   TupleDesc   tupdesc = RelationGetDescr(rel);
 +   int         i;
 +   char       *colname;
 +   List       *options;
 +   ListCell   *lc;
 +   bool        first = true;
 +
 +   appendStringInfo(buf, "SELECT ");
 +   for (i = 0; i < tupdesc->natts; i++)
 +   {
 +       /* Ignore dropped columns. */
 +       if (tupdesc->attrs[i]->attisdropped)
 +           continue;
 +
 +       /* Use attribute name or column_name option. */
 +       colname = NameStr(tupdesc->attrs[i]->attname);
 +       options = GetForeignColumnOptions(relid, i + 1);
 +
 +       foreach(lc, options)
 +       {
 +           DefElem    *def = (DefElem *) lfirst(lc);
 +
 +           if (strcmp(def->defname, "column_name") == 0)
 +           {
 +               colname = defGetString(def);
 +               break;
 +           }
 +       }
 +
 +       if (!first)
 +           appendStringInfo(buf, ", ");
 +       appendStringInfoString(buf, quote_identifier(colname));
 +       first = false;
 +   }
 +
 +   /* Don't generate bad syntax for zero-column relation. */
 +   if (first)
 +       appendStringInfo(buf, "NULL");
 +
 +   /*
 +    * Construct FROM clause
 +    */
 +   appendStringInfo(buf, " FROM ");
 +   deparseRelation(buf, relid);
 +}
 +
 +/*
 + * Construct name to use for given column, and emit it into buf.
 + * If it has a column_name FDW option, use that instead of attribute name.
 + */
 +static void
 +deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root)
 +{
 +   RangeTblEntry *rte;
 +   char       *colname = NULL;
 +   List       *options;
 +   ListCell   *lc;
 +
 +   /* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */
 +   Assert(varno >= 1 && varno <= root->simple_rel_array_size);
 +
 +   /* Get RangeTblEntry from array in PlannerInfo. */
 +   rte = root->simple_rte_array[varno];
 +
 +   /*
 +    * If it's a column of a foreign table, and it has the column_name FDW
 +    * option, use that value.
 +    */
 +   options = GetForeignColumnOptions(rte->relid, varattno);
 +   foreach(lc, options)
 +   {
 +       DefElem    *def = (DefElem *) lfirst(lc);
 +
 +       if (strcmp(def->defname, "column_name") == 0)
 +       {
 +           colname = defGetString(def);
 +           break;
 +       }
 +   }
 +
 +   /*
 +    * If it's a column of a regular table or it doesn't have column_name FDW
 +    * option, use attribute name.
 +    */
 +   if (colname == NULL)
 +       colname = get_relid_attribute_name(rte->relid, varattno);
 +
 +   appendStringInfoString(buf, quote_identifier(colname));
 +}
 +
 +/*
 + * Append remote name of specified foreign table to buf.
 + * Use value of table_name FDW option (if any) instead of relation's name.
 + * Similarly, schema_name FDW option overrides schema name.
 + */
 +static void
 +deparseRelation(StringInfo buf, Oid relid)
 +{
 +   ForeignTable *table;
 +   const char *nspname = NULL;
 +   const char *relname = NULL;
 +   ListCell   *lc;
 +
 +   /* obtain additional catalog information. */
 +   table = GetForeignTable(relid);
 +
 +   /*
 +    * Use value of FDW options if any, instead of the name of object itself.
 +    */
 +   foreach(lc, table->options)
 +   {
 +       DefElem    *def = (DefElem *) lfirst(lc);
 +
 +       if (strcmp(def->defname, "schema_name") == 0)
 +           nspname = defGetString(def);
 +       else if (strcmp(def->defname, "table_name") == 0)
 +           relname = defGetString(def);
 +   }
 +
 +   if (nspname == NULL)
 +       nspname = get_namespace_name(get_rel_namespace(relid));
 +   if (relname == NULL)
 +       relname = get_rel_name(relid);
 +
 +   appendStringInfo(buf, "%s.%s",
 +                    quote_identifier(nspname), quote_identifier(relname));
 +}
 +
 +/*
 + * Append a SQL string literal representing "val" to buf.
 + */
 +static void
 +deparseStringLiteral(StringInfo buf, const char *val)
 +{
 +   const char *valptr;
 +
 +   /*
 +    * Rather than making assumptions about the remote server's value of
 +    * standard_conforming_strings, always use E'foo' syntax if there are any
 +    * backslashes.  This will fail on remote servers before 8.1, but those
 +    * are long out of support.
 +    */
 +   if (strchr(val, '\\') != NULL)
 +       appendStringInfoChar(buf, ESCAPE_STRING_SYNTAX);
 +   appendStringInfoChar(buf, '\'');
 +   for (valptr = val; *valptr; valptr++)
 +   {
 +       char        ch = *valptr;
 +
 +       if (SQL_STR_DOUBLE(ch, true))
 +           appendStringInfoChar(buf, ch);
 +       appendStringInfoChar(buf, ch);
 +   }
 +   appendStringInfoChar(buf, '\'');
 +}
 +
 +/*
 + * Deparse given expression into buf.
 + *
 + * This function must support all the same node types that foreign_expr_walker
 + * accepts.
 + *
 + * Note: unlike ruleutils.c, we just use a simple hard-wired parenthesization
 + * scheme: anything more complex than a Var, Const, function call or cast
 + * should be self-parenthesized.
 + */
 +static void
 +deparseExpr(StringInfo buf, Expr *node, PlannerInfo *root)
 +{
 +   if (node == NULL)
 +       return;
 +
 +   switch (nodeTag(node))
 +   {
 +       case T_Var:
 +           deparseVar(buf, (Var *) node, root);
 +           break;
 +       case T_Const:
 +           deparseConst(buf, (Const *) node, root);
 +           break;
 +       case T_Param:
 +           deparseParam(buf, (Param *) node, root);
 +           break;
 +       case T_ArrayRef:
 +           deparseArrayRef(buf, (ArrayRef *) node, root);
 +           break;
 +       case T_FuncExpr:
 +           deparseFuncExpr(buf, (FuncExpr *) node, root);
 +           break;
 +       case T_OpExpr:
 +           deparseOpExpr(buf, (OpExpr *) node, root);
 +           break;
 +       case T_DistinctExpr:
 +           deparseDistinctExpr(buf, (DistinctExpr *) node, root);
 +           break;
 +       case T_ScalarArrayOpExpr:
 +           deparseScalarArrayOpExpr(buf, (ScalarArrayOpExpr *) node, root);
 +           break;
 +       case T_RelabelType:
 +           deparseRelabelType(buf, (RelabelType *) node, root);
 +           break;
 +       case T_BoolExpr:
 +           deparseBoolExpr(buf, (BoolExpr *) node, root);
 +           break;
 +       case T_NullTest:
 +           deparseNullTest(buf, (NullTest *) node, root);
 +           break;
 +       case T_ArrayExpr:
 +           deparseArrayExpr(buf, (ArrayExpr *) node, root);
 +           break;
 +       default:
 +           elog(ERROR, "unsupported expression type for deparse: %d",
 +                (int) nodeTag(node));
 +           break;
 +   }
 +}
 +
 +/*
 + * Deparse given Var node into buf.
 + */
 +static void
 +deparseVar(StringInfo buf, Var *node, PlannerInfo *root)
 +{
 +   Assert(node->varlevelsup == 0);
 +   deparseColumnRef(buf, node->varno, node->varattno, root);
 +}
 +
 +/*
 + * Deparse given constant value into buf.
 + *
 + * This function has to be kept in sync with ruleutils.c's get_const_expr.
 + */
 +static void
 +deparseConst(StringInfo buf, Const *node, PlannerInfo *root)
 +{
 +   Oid         typoutput;
 +   bool        typIsVarlena;
 +   char       *extval;
 +   bool        isfloat = false;
 +   bool        needlabel;
 +
 +   if (node->constisnull)
 +   {
 +       appendStringInfo(buf, "NULL");
 +       appendStringInfo(buf, "::%s",
 +                        format_type_with_typemod(node->consttype,
 +                                                 node->consttypmod));
 +       return;
 +   }
 +
 +   getTypeOutputInfo(node->consttype,
 +                     &typoutput, &typIsVarlena);
 +   extval = OidOutputFunctionCall(typoutput, node->constvalue);
 +
 +   switch (node->consttype)
 +   {
 +       case INT2OID:
 +       case INT4OID:
 +       case INT8OID:
 +       case OIDOID:
 +       case FLOAT4OID:
 +       case FLOAT8OID:
 +       case NUMERICOID:
 +           {
 +               /*
 +                * No need to quote unless it's a special value such as 'NaN'.
 +                * See comments in get_const_expr().
 +                */
 +               if (strspn(extval, "0123456789+-eE.") == strlen(extval))
 +               {
 +                   if (extval[0] == '+' || extval[0] == '-')
 +                       appendStringInfo(buf, "(%s)", extval);
 +                   else
 +                       appendStringInfoString(buf, extval);
 +                   if (strcspn(extval, "eE.") != strlen(extval))
 +                       isfloat = true; /* it looks like a float */
 +               }
 +               else
 +                   appendStringInfo(buf, "'%s'", extval);
 +           }
 +           break;
 +       case BITOID:
 +       case VARBITOID:
 +           appendStringInfo(buf, "B'%s'", extval);
 +           break;
 +       case BOOLOID:
 +           if (strcmp(extval, "t") == 0)
 +               appendStringInfoString(buf, "true");
 +           else
 +               appendStringInfoString(buf, "false");
 +           break;
 +       default:
 +           deparseStringLiteral(buf, extval);
 +           break;
 +   }
 +
 +   /*
 +    * Append ::typename unless the constant will be implicitly typed as the
 +    * right type when it is read in.
 +    *
 +    * XXX this code has to be kept in sync with the behavior of the parser,
 +    * especially make_const.
 +    */
 +   switch (node->consttype)
 +   {
 +       case BOOLOID:
 +       case INT4OID:
 +       case UNKNOWNOID:
 +           needlabel = false;
 +           break;
 +       case NUMERICOID:
 +           needlabel = !isfloat || (node->consttypmod >= 0);
 +           break;
 +       default:
 +           needlabel = true;
 +           break;
 +   }
 +   if (needlabel)
 +       appendStringInfo(buf, "::%s",
 +                        format_type_with_typemod(node->consttype,
 +                                                 node->consttypmod));
 +}
 +
 +/*
 + * Deparse given Param node into buf.
 + *
 + * We don't need to renumber the parameter ID, because the executor functions
 + * in postgres_fdw.c preserve the numbering of PARAM_EXTERN Params.
 + * (This might change soon.)
 + */
 +static void
 +deparseParam(StringInfo buf, Param *node, PlannerInfo *root)
 +{
 +   Assert(node->paramkind == PARAM_EXTERN);
 +   appendStringInfo(buf, "$%d", node->paramid);
 +}
 +
 +/*
 + * Deparse an array subscript expression.
 + */
 +static void
 +deparseArrayRef(StringInfo buf, ArrayRef *node, PlannerInfo *root)
 +{
 +   ListCell   *lowlist_item;
 +   ListCell   *uplist_item;
 +
 +   /* Always parenthesize the expression. */
 +   appendStringInfoChar(buf, '(');
 +
 +   /*
 +    * Deparse referenced array expression first.  If that expression includes
 +    * a cast, we have to parenthesize to prevent the array subscript from
 +    * being taken as typename decoration.  We can avoid that in the typical
 +    * case of subscripting a Var, but otherwise do it.
 +    */
 +   if (IsA(node->refexpr, Var))
 +       deparseExpr(buf, node->refexpr, root);
 +   else
 +   {
 +       appendStringInfoChar(buf, '(');
 +       deparseExpr(buf, node->refexpr, root);
 +       appendStringInfoChar(buf, ')');
 +   }
 +
 +   /* Deparse subscript expressions. */
 +   lowlist_item = list_head(node->reflowerindexpr);    /* could be NULL */
 +   foreach(uplist_item, node->refupperindexpr)
 +   {
 +       appendStringInfoChar(buf, '[');
 +       if (lowlist_item)
 +       {
 +           deparseExpr(buf, lfirst(lowlist_item), root);
 +           appendStringInfoChar(buf, ':');
 +           lowlist_item = lnext(lowlist_item);
 +       }
 +       deparseExpr(buf, lfirst(uplist_item), root);
 +       appendStringInfoChar(buf, ']');
 +   }
 +
 +   appendStringInfoChar(buf, ')');
 +}
 +
 +/*
 + * Deparse given node which represents a function call into buf.
 + *
 + * Here not only explicit function calls and explicit casts but also implicit
 + * casts are deparsed to avoid problems caused by different cast settings
 + * between local and remote.
 + *
 + * Function name is always qualified by schema name to avoid problems caused
 + * by different setting of search_path on remote side.
 + */
 +static void
 +deparseFuncExpr(StringInfo buf, FuncExpr *node, PlannerInfo *root)
 +{
 +   HeapTuple   proctup;
 +   Form_pg_proc procform;
 +   const char *proname;
 +   const char *schemaname;
 +   bool        use_variadic;
 +   bool        first;
 +   ListCell   *arg;
 +
 +   proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(node->funcid));
 +   if (!HeapTupleIsValid(proctup))
 +       elog(ERROR, "cache lookup failed for function %u", node->funcid);
 +   procform = (Form_pg_proc) GETSTRUCT(proctup);
 +   proname = NameStr(procform->proname);
 +
 +   /* Check if need to print VARIADIC (cf. ruleutils.c) */
 +   if (OidIsValid(procform->provariadic))
 +   {
 +       if (procform->provariadic != ANYOID)
 +           use_variadic = true;
 +       else
 +           use_variadic = node->funcvariadic;
 +   }
 +   else
 +       use_variadic = false;
 +
 +   /* Deparse the function name ... */
 +   schemaname = get_namespace_name(procform->pronamespace);
 +   appendStringInfo(buf, "%s.%s(",
 +                    quote_identifier(schemaname),
 +                    quote_identifier(proname));
 +   /* ... and all the arguments */
 +   first = true;
 +   foreach(arg, node->args)
 +   {
 +       if (!first)
 +           appendStringInfoString(buf, ", ");
 +       if (use_variadic && lnext(arg) == NULL)
 +           appendStringInfoString(buf, "VARIADIC ");
 +       deparseExpr(buf, (Expr *) lfirst(arg), root);
 +       first = false;
 +   }
 +   appendStringInfoChar(buf, ')');
 +
 +   ReleaseSysCache(proctup);
 +}
 +
 +/*
 + * Deparse given operator expression into buf. To avoid problems around
 + * priority of operations, we always parenthesize the arguments.  Also we use
 + * OPERATOR(schema.operator) notation to determine remote operator exactly.
 + */
 +static void
 +deparseOpExpr(StringInfo buf, OpExpr *node, PlannerInfo *root)
 +{
 +   HeapTuple   tuple;
 +   Form_pg_operator form;
 +   const char *opnspname;
 +   char       *opname;
 +   char        oprkind;
 +   ListCell   *arg;
 +
 +   /* Retrieve information about the operator from system catalog. */
 +   tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno));
 +   if (!HeapTupleIsValid(tuple))
 +       elog(ERROR, "cache lookup failed for operator %u", node->opno);
 +   form = (Form_pg_operator) GETSTRUCT(tuple);
 +
 +   opnspname = quote_identifier(get_namespace_name(form->oprnamespace));
 +   /* opname is not a SQL identifier, so we don't need to quote it. */
 +   opname = NameStr(form->oprname);
 +   oprkind = form->oprkind;
 +
 +   /* Sanity check. */
 +   Assert((oprkind == 'r' && list_length(node->args) == 1) ||
 +          (oprkind == 'l' && list_length(node->args) == 1) ||
 +          (oprkind == 'b' && list_length(node->args) == 2));
 +
 +   /* Always parenthesize the expression. */
 +   appendStringInfoChar(buf, '(');
 +
 +   /* Deparse left operand. */
 +   if (oprkind == 'r' || oprkind == 'b')
 +   {
 +       arg = list_head(node->args);
 +       deparseExpr(buf, lfirst(arg), root);
 +       appendStringInfoChar(buf, ' ');
 +   }
 +
 +   /* Deparse fully qualified operator name. */
 +   appendStringInfo(buf, "OPERATOR(%s.%s)", opnspname, opname);
 +
 +   /* Deparse right operand. */
 +   if (oprkind == 'l' || oprkind == 'b')
 +   {
 +       arg = list_tail(node->args);
 +       appendStringInfoChar(buf, ' ');
 +       deparseExpr(buf, lfirst(arg), root);
 +   }
 +
 +   appendStringInfoChar(buf, ')');
 +
 +   ReleaseSysCache(tuple);
 +}
 +
 +/*
 + * Deparse IS DISTINCT FROM.
 + */
 +static void
 +deparseDistinctExpr(StringInfo buf, DistinctExpr *node, PlannerInfo *root)
 +{
 +   Assert(list_length(node->args) == 2);
 +
 +   appendStringInfoChar(buf, '(');
 +   deparseExpr(buf, linitial(node->args), root);
 +   appendStringInfo(buf, " IS DISTINCT FROM ");
 +   deparseExpr(buf, lsecond(node->args), root);
 +   appendStringInfoChar(buf, ')');
 +}
 +
 +/*
 + * Deparse given ScalarArrayOpExpr expression into buf.  To avoid problems
 + * around priority of operations, we always parenthesize the arguments.  Also
 + * we use OPERATOR(schema.operator) notation to determine remote operator
 + * exactly.
 + */
 +static void
 +deparseScalarArrayOpExpr(StringInfo buf,
 +                        ScalarArrayOpExpr *node,
 +                        PlannerInfo *root)
 +{
 +   HeapTuple   tuple;
 +   Form_pg_operator form;
 +   const char *opnspname;
 +   char       *opname;
 +   Expr       *arg1;
 +   Expr       *arg2;
 +
 +   /* Retrieve information about the operator from system catalog. */
 +   tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno));
 +   if (!HeapTupleIsValid(tuple))
 +       elog(ERROR, "cache lookup failed for operator %u", node->opno);
 +   form = (Form_pg_operator) GETSTRUCT(tuple);
 +
 +   opnspname = quote_identifier(get_namespace_name(form->oprnamespace));
 +   /* opname is not a SQL identifier, so we don't need to quote it. */
 +   opname = NameStr(form->oprname);
 +
 +   /* Sanity check. */
 +   Assert(list_length(node->args) == 2);
 +
 +   /* Always parenthesize the expression. */
 +   appendStringInfoChar(buf, '(');
 +
 +   /* Deparse left operand. */
 +   arg1 = linitial(node->args);
 +   deparseExpr(buf, arg1, root);
 +
 +   /* Deparse fully qualified operator name plus decoration. */
 +   appendStringInfo(buf, " OPERATOR(%s.%s) %s (",
 +                    opnspname, opname, node->useOr ? "ANY" : "ALL");
 +
 +   /* Deparse right operand. */
 +   arg2 = lsecond(node->args);
 +   deparseExpr(buf, arg2, root);
 +
 +   appendStringInfoChar(buf, ')');
 +
 +   /* Always parenthesize the expression. */
 +   appendStringInfoChar(buf, ')');
 +
 +   ReleaseSysCache(tuple);
 +}
 +
 +/*
 + * Deparse a RelabelType (binary-compatible cast) node.
 + */
 +static void
 +deparseRelabelType(StringInfo buf, RelabelType *node, PlannerInfo *root)
 +{
 +   deparseExpr(buf, node->arg, root);
 +   appendStringInfo(buf, "::%s",
 +                    format_type_with_typemod(node->resulttype,
 +                                             node->resulttypmod));
 +}
 +
 +/*
 + * Deparse a BoolExpr node.
 + *
 + * Note: by the time we get here, AND and OR expressions have been flattened
 + * into N-argument form, so we'd better be prepared to deal with that.
 + */
 +static void
 +deparseBoolExpr(StringInfo buf, BoolExpr *node, PlannerInfo *root)
 +{
 +   const char *op = NULL;      /* keep compiler quiet */
 +   bool        first;
 +   ListCell   *lc;
 +
 +   switch (node->boolop)
 +   {
 +       case AND_EXPR:
 +           op = "AND";
 +           break;
 +       case OR_EXPR:
 +           op = "OR";
 +           break;
 +       case NOT_EXPR:
 +           appendStringInfo(buf, "(NOT ");
 +           deparseExpr(buf, linitial(node->args), root);
 +           appendStringInfoChar(buf, ')');
 +           return;
 +   }
 +
 +   appendStringInfoChar(buf, '(');
 +   first = true;
 +   foreach(lc, node->args)
 +   {
 +       if (!first)
 +           appendStringInfo(buf, " %s ", op);
 +       deparseExpr(buf, (Expr *) lfirst(lc), root);
 +       first = false;
 +   }
 +   appendStringInfoChar(buf, ')');
 +}
 +
 +/*
 + * Deparse IS [NOT] NULL expression.
 + */
 +static void
 +deparseNullTest(StringInfo buf, NullTest *node, PlannerInfo *root)
 +{
 +   appendStringInfoChar(buf, '(');
 +   deparseExpr(buf, node->arg, root);
 +   if (node->nulltesttype == IS_NULL)
 +       appendStringInfo(buf, " IS NULL)");
 +   else
 +       appendStringInfo(buf, " IS NOT NULL)");
 +}
 +
 +/*
 + * Deparse ARRAY[...] construct.
 + */
 +static void
 +deparseArrayExpr(StringInfo buf, ArrayExpr *node, PlannerInfo *root)
 +{
 +   bool        first = true;
 +   ListCell   *lc;
 +
 +   appendStringInfo(buf, "ARRAY[");
 +   foreach(lc, node->elements)
 +   {
 +       if (!first)
 +           appendStringInfo(buf, ", ");
 +       deparseExpr(buf, lfirst(lc), root);
 +       first = false;
 +   }
 +   appendStringInfoChar(buf, ']');
 +
 +   /* If the array is empty, we need an explicit cast to the array type. */
 +   if (node->elements == NIL)
 +       appendStringInfo(buf, "::%s",
 +                        format_type_with_typemod(node->array_typeid, -1));
 +}
      --- /dev/null
   +-- ===================================================================
 +-- create FDW objects
 +-- ===================================================================
 +CREATE EXTENSION postgres_fdw;
 +CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw;
 +CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw
 +  OPTIONS (dbname 'contrib_regression');
 +CREATE USER MAPPING FOR public SERVER testserver1
 +   OPTIONS (user 'value', password 'value');
 +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
 +-- ===================================================================
 +-- create objects used through FDW loopback server
 +-- ===================================================================
 +CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz');
 +CREATE SCHEMA "S 1";
 +CREATE TABLE "S 1"."T 1" (
 +   "C 1" int NOT NULL,
 +   c2 int NOT NULL,
 +   c3 text,
 +   c4 timestamptz,
 +   c5 timestamp,
 +   c6 varchar(10),
 +   c7 char(10),
 +   c8 user_enum,
 +   CONSTRAINT t1_pkey PRIMARY KEY ("C 1")
 +);
 +CREATE TABLE "S 1"."T 2" (
 +   c1 int NOT NULL,
 +   c2 text,
 +   CONSTRAINT t2_pkey PRIMARY KEY (c1)
 +);
 +INSERT INTO "S 1"."T 1"
 +   SELECT id,
 +          id % 10,
 +          to_char(id, 'FM00000'),
 +          '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval,
 +          '1970-01-01'::timestamp + ((id % 100) || ' days')::interval,
 +          id % 10,
 +          id % 10,
 +          'foo'::user_enum
 +   FROM generate_series(1, 1000) id;
 +INSERT INTO "S 1"."T 2"
 +   SELECT id,
 +          'AAA' || to_char(id, 'FM000')
 +   FROM generate_series(1, 100) id;
 +ANALYZE "S 1"."T 1";
 +ANALYZE "S 1"."T 2";
 +-- ===================================================================
 +-- create foreign tables
 +-- ===================================================================
 +CREATE FOREIGN TABLE ft1 (
 +   c0 int,
 +   c1 int NOT NULL,
 +   c2 int NOT NULL,
 +   c3 text,
 +   c4 timestamptz,
 +   c5 timestamp,
 +   c6 varchar(10),
 +   c7 char(10),
 +   c8 user_enum
 +) SERVER loopback;
 +ALTER FOREIGN TABLE ft1 DROP COLUMN c0;
 +CREATE FOREIGN TABLE ft2 (
 +   c0 int,
 +   c1 int NOT NULL,
 +   c2 int NOT NULL,
 +   c3 text,
 +   c4 timestamptz,
 +   c5 timestamp,
 +   c6 varchar(10),
 +   c7 char(10),
 +   c8 user_enum
 +) SERVER loopback;
 +ALTER FOREIGN TABLE ft2 DROP COLUMN c0;
 +-- ===================================================================
 +-- tests for validator
 +-- ===================================================================
 +-- requiressl, krbsrvname and gsslib are omitted because they depend on
 +-- configure options
 +ALTER SERVER testserver1 OPTIONS (
 +   use_remote_explain 'false',
 +   fdw_startup_cost '123.456',
 +   fdw_tuple_cost '0.123',
 +   service 'value',
 +   connect_timeout 'value',
 +   dbname 'value',
 +   host 'value',
 +   hostaddr 'value',
 +   port 'value',
 +   --client_encoding 'value',
 +   application_name 'value',
 +   --fallback_application_name 'value',
 +   keepalives 'value',
 +   keepalives_idle 'value',
 +   keepalives_interval 'value',
 +   -- requiressl 'value',
 +   sslcompression 'value',
 +   sslmode 'value',
 +   sslcert 'value',
 +   sslkey 'value',
 +   sslrootcert 'value',
 +   sslcrl 'value'
 +   --requirepeer 'value',
 +   -- krbsrvname 'value',
 +   -- gsslib 'value',
 +   --replication 'value'
 +);
 +ALTER USER MAPPING FOR public SERVER testserver1
 +   OPTIONS (DROP user, DROP password);
 +ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1');
 +ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
 +ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
 +ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
 +\det+
 +                             List of foreign tables
 + Schema | Table |  Server  |              FDW Options              | Description 
 +--------+-------+----------+---------------------------------------+-------------
 + public | ft1   | loopback | (schema_name 'S 1', table_name 'T 1') | 
 + public | ft2   | loopback | (schema_name 'S 1', table_name 'T 1') | 
 +(2 rows)
 +
 +-- Now we should be able to run ANALYZE.
 +-- To exercise multiple code paths, we use local stats on ft1
 +-- and remote_explain mode on ft2.
 +ANALYZE ft1;
 +ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_explain 'true');
 +-- ===================================================================
 +-- simple queries
 +-- ===================================================================
 +-- single table, with/without alias
 +EXPLAIN (COSTS false) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
 +           QUERY PLAN            
 +---------------------------------
 + Limit
 +   ->  Sort
 +         Sort Key: c3, c1
 +         ->  Foreign Scan on ft1
 +(4 rows)
 +
 +SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
 + c1  | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
 +-----+----+-------+------------------------------+--------------------------+----+------------+-----
 + 101 |  1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
 + 102 |  2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2  | 2          | foo
 + 103 |  3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3  | 3          | foo
 + 104 |  4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4  | 4          | foo
 + 105 |  5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
 + 106 |  6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
 + 107 |  7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
 + 108 |  8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
 + 109 |  9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9  | 9          | foo
 + 110 |  0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0  | 0          | foo
 +(10 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
 +                                     QUERY PLAN                                      
 +-------------------------------------------------------------------------------------
 + Limit
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   ->  Sort
 +         Output: c1, c2, c3, c4, c5, c6, c7, c8
 +         Sort Key: t1.c3, t1.c1
 +         ->  Foreign Scan on public.ft1 t1
 +               Output: c1, c2, c3, c4, c5, c6, c7, c8
 +               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
 +(8 rows)
 +
 +SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
 + c1  | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
 +-----+----+-------+------------------------------+--------------------------+----+------------+-----
 + 101 |  1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
 + 102 |  2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2  | 2          | foo
 + 103 |  3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3  | 3          | foo
 + 104 |  4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4  | 4          | foo
 + 105 |  5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
 + 106 |  6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
 + 107 |  7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
 + 108 |  8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
 + 109 |  9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9  | 9          | foo
 + 110 |  0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0  | 0          | foo
 +(10 rows)
 +
 +-- empty result
 +SELECT * FROM ft1 WHERE false;
 + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 
 +----+----+----+----+----+----+----+----
 +(0 rows)
 +
 +-- with WHERE clause
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
 +                                                                                                     QUERY PLAN                                                                                                      
 +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c7 OPERATOR(pg_catalog.>=) '1'::bpchar)) AND (("C 1" OPERATOR(pg_catalog.=) 101)) AND ((c6::text OPERATOR(pg_catalog.=) '1'::text))
 +(3 rows)
 +
 +SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
 + c1  | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
 +-----+----+-------+------------------------------+--------------------------+----+------------+-----
 + 101 |  1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
 +(1 row)
 +
 +-- aggregate
 +SELECT COUNT(*) FROM ft1 t1;
 + count 
 +-------
 +  1000
 +(1 row)
 +
 +-- join two tables
 +SELECT t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
 + c1  
 +-----
 + 101
 + 102
 + 103
 + 104
 + 105
 + 106
 + 107
 + 108
 + 109
 + 110
 +(10 rows)
 +
 +-- subquery
 +SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1;
 + c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
 +----+----+-------+------------------------------+--------------------------+----+------------+-----
 +  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
 +  2 |  2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2  | 2          | foo
 +  3 |  3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3  | 3          | foo
 +  4 |  4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4  | 4          | foo
 +  5 |  5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
 +  6 |  6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
 +  7 |  7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
 +  8 |  8 | 00008 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
 +  9 |  9 | 00009 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9  | 9          | foo
 + 10 |  0 | 00010 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0  | 0          | foo
 +(10 rows)
 +
 +-- subquery+MAX
 +SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1;
 +  c1  | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
 +------+----+-------+------------------------------+--------------------------+----+------------+-----
 + 1000 |  0 | 01000 | Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970 | 0  | 0          | foo
 +(1 row)
 +
 +-- used in CTE
 +WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1;
 + c1 | c2 |  c3   |              c4              
 +----+----+-------+------------------------------
 +  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST
 +  2 |  2 | 00002 | Sat Jan 03 00:00:00 1970 PST
 +  3 |  3 | 00003 | Sun Jan 04 00:00:00 1970 PST
 +  4 |  4 | 00004 | Mon Jan 05 00:00:00 1970 PST
 +  5 |  5 | 00005 | Tue Jan 06 00:00:00 1970 PST
 +  6 |  6 | 00006 | Wed Jan 07 00:00:00 1970 PST
 +  7 |  7 | 00007 | Thu Jan 08 00:00:00 1970 PST
 +  8 |  8 | 00008 | Fri Jan 09 00:00:00 1970 PST
 +  9 |  9 | 00009 | Sat Jan 10 00:00:00 1970 PST
 + 10 |  0 | 00010 | Sun Jan 11 00:00:00 1970 PST
 +(10 rows)
 +
 +-- fixed values
 +SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1;
 + ?column? | ?column? 
 +----------+----------
 + fixed    | 
 +(1 row)
 +
 +-- user-defined operator/function
 +CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$
 +BEGIN
 +RETURN abs($1);
 +END
 +$$ LANGUAGE plpgsql IMMUTABLE;
 +CREATE OPERATOR === (
 +    LEFTARG = int,
 +    RIGHTARG = int,
 +    PROCEDURE = int4eq,
 +    COMMUTATOR = ===,
 +    NEGATOR = !==
 +);
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2);
 +                               QUERY PLAN                                
 +-------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Filter: (t1.c1 = postgres_fdw_abs(t1.c2))
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
 +(4 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2;
 +                               QUERY PLAN                                
 +-------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Filter: (t1.c1 === t1.c2)
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
 +(4 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
 +                                                            QUERY PLAN                                                             
 +-----------------------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) pg_catalog.abs(c2)))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
 +                                                    QUERY PLAN                                                     
 +-------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) c2))
 +(3 rows)
 +
 +-- ===================================================================
 +-- WHERE with remotely-executable conditions
 +-- ===================================================================
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 1;         -- Var, OpExpr(b), Const
 +                                                    QUERY PLAN                                                    
 +------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr
 +                                                                       QUERY PLAN                                                                       
 +--------------------------------------------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 100)) AND ((c2 OPERATOR(pg_catalog.=) 0))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NULL;        -- NullTest
 +                                           QUERY PLAN                                            
 +-------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NULL))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL;    -- NullTest
 +                                             QUERY PLAN                                              
 +-----------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NOT NULL))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr
 +                                                                                      QUERY PLAN                                                                                      
 +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((pg_catalog.round(pg_catalog."numeric"(pg_catalog.abs("C 1")), 0) OPERATOR(pg_catalog.=) 1::numeric))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = -c1;          -- OpExpr(l)
 +                                                                  QUERY PLAN                                                                   
 +-----------------------------------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) (OPERATOR(pg_catalog.-) "C 1")))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE 1 = c1!;           -- OpExpr(r)
 +                                                                             QUERY PLAN                                                                              
 +---------------------------------------------------------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((1::numeric OPERATOR(pg_catalog.=) (pg_catalog.int8("C 1") OPERATOR(pg_catalog.!))))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
 +                                                                 QUERY PLAN                                                                 
 +--------------------------------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" IS NOT NULL) IS DISTINCT FROM ("C 1" IS NOT NULL)))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
 +                                                                             QUERY PLAN                                                                              
 +---------------------------------------------------------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) ANY (ARRAY[c2, 1, ("C 1" OPERATOR(pg_catalog.+) 0)])))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef
 +                                                                QUERY PLAN                                                                 
 +-------------------------------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) ((ARRAY["C 1", c2, 3])[1])))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar';  -- check special chars
 +                                                               QUERY PLAN                                                               
 +----------------------------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c6::text OPERATOR(pg_catalog.=) E'foo''s\\bar'::text))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c8 = 'foo';  -- can't be sent to remote
 +                               QUERY PLAN                                
 +-------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Filter: (t1.c8 = 'foo'::user_enum)
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
 +(4 rows)
 +
 +-- ===================================================================
 +-- parameterized queries
 +-- ===================================================================
 +-- simple join
 +PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st1(1, 2);
 +                                                            QUERY PLAN                                                             
 +-----------------------------------------------------------------------------------------------------------------------------------
 + Nested Loop
 +   Output: t1.c3, t2.c3
 +   ->  Foreign Scan on public.ft1 t1
 +         Output: t1.c3
 +         Remote SQL: SELECT NULL, NULL, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
 +   ->  Foreign Scan on public.ft2 t2
 +         Output: t2.c3
 +         Remote SQL: SELECT NULL, NULL, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 2))
 +(8 rows)
 +
 +EXECUTE st1(1, 1);
 +  c3   |  c3   
 +-------+-------
 + 00001 | 00001
 +(1 row)
 +
 +EXECUTE st1(101, 101);
 +  c3   |  c3   
 +-------+-------
 + 00101 | 00101
 +(1 row)
 +
 +-- subquery using stable function (can't be sent to remote)
 +PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c4) = 6) ORDER BY c1;
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20);
 +                                                                  QUERY PLAN                                                                  
 +----------------------------------------------------------------------------------------------------------------------------------------------
 + Sort
 +   Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
 +   Sort Key: t1.c1
 +   ->  Nested Loop Semi Join
 +         Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
 +         Join Filter: (t1.c3 = t2.c3)
 +         ->  Foreign Scan on public.ft1 t1
 +               Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
 +               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.<) 20))
 +         ->  Materialize
 +               Output: t2.c3
 +               ->  Foreign Scan on public.ft2 t2
 +                     Output: t2.c3
 +                     Filter: (date_part('dow'::text, t2.c4) = 6::double precision)
 +                     Remote SQL: SELECT NULL, NULL, c3, c4, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.>) 10))
 +(15 rows)
 +
 +EXECUTE st2(10, 20);
 + c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
 +----+----+-------+------------------------------+--------------------------+----+------------+-----
 + 16 |  6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
 +(1 row)
 +
 +EXECUTE st1(101, 101);
 +  c3   |  c3   
 +-------+-------
 + 00101 | 00101
 +(1 row)
 +
 +-- subquery using immutable function (can be sent to remote)
 +PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c5) = 6) ORDER BY c1;
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20);
 +                                                                                                               QUERY PLAN                                                                                                                
 +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 + Sort
 +   Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
 +   Sort Key: t1.c1
 +   ->  Nested Loop Semi Join
 +         Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
 +         Join Filter: (t1.c3 = t2.c3)
 +         ->  Foreign Scan on public.ft1 t1
 +               Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
 +               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.<) 20))
 +         ->  Materialize
 +               Output: t2.c3
 +               ->  Foreign Scan on public.ft2 t2
 +                     Output: t2.c3
 +                     Remote SQL: SELECT NULL, NULL, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.>) 10)) AND ((pg_catalog.date_part('dow'::text, c5) OPERATOR(pg_catalog.=) 6::double precision))
 +(14 rows)
 +
 +EXECUTE st3(10, 20);
 + c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
 +----+----+-------+------------------------------+--------------------------+----+------------+-----
 + 16 |  6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
 +(1 row)
 +
 +EXECUTE st3(20, 30);
 + c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
 +----+----+-------+------------------------------+--------------------------+----+------------+-----
 + 23 |  3 | 00023 | Sat Jan 24 00:00:00 1970 PST | Sat Jan 24 00:00:00 1970 | 3  | 3          | foo
 +(1 row)
 +
 +-- custom plan should be chosen initially
 +PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1;
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
 +                                                    QUERY PLAN                                                    
 +------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
 +                                                    QUERY PLAN                                                    
 +------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
 +                                                    QUERY PLAN                                                    
 +------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
 +                                                    QUERY PLAN                                                    
 +------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
 +(3 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
 +                                                    QUERY PLAN                                                    
 +------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
 +(3 rows)
 +
 +-- once we try it enough times, should switch to generic plan
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
 +                                                    QUERY PLAN                                                     
 +-------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) $1))
 +(3 rows)
 +
 +-- value of $1 should not be sent to remote
 +PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2;
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
 +                                                    QUERY PLAN                                                    
 +------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Filter: (t1.c8 = 'foo'::user_enum)
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
 +(4 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
 +                                                    QUERY PLAN                                                    
 +------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Filter: (t1.c8 = 'foo'::user_enum)
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
 +(4 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
 +                                                    QUERY PLAN                                                    
 +------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Filter: (t1.c8 = 'foo'::user_enum)
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
 +(4 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
 +                                                    QUERY PLAN                                                    
 +------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Filter: (t1.c8 = 'foo'::user_enum)
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
 +(4 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
 +                                                    QUERY PLAN                                                    
 +------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Filter: (t1.c8 = 'foo'::user_enum)
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
 +(4 rows)
 +
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
 +                                                    QUERY PLAN                                                     
 +-------------------------------------------------------------------------------------------------------------------
 + Foreign Scan on public.ft1 t1
 +   Output: c1, c2, c3, c4, c5, c6, c7, c8
 +   Filter: (t1.c8 = $1)
 +   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) $2))
 +(4 rows)
 +
 +EXECUTE st5('foo', 1);
 + c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
 +----+----+-------+------------------------------+--------------------------+----+------------+-----
 +  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
 +(1 row)
 +
 +-- cleanup
 +DEALLOCATE st1;
 +DEALLOCATE st2;
 +DEALLOCATE st3;
 +DEALLOCATE st4;
 +DEALLOCATE st5;
 +-- ===================================================================
 +-- used in pl/pgsql function
 +-- ===================================================================
 +CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$
 +DECLARE
 +   v_c1 int;
 +BEGIN
 +    SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1;
 +    PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1;
 +    RETURN v_c1;
 +END;
 +$$ LANGUAGE plpgsql;
 +SELECT f_test(100);
 + f_test 
 +--------
 +    100
 +(1 row)
 +
 +DROP FUNCTION f_test(int);
 +-- ===================================================================
 +-- conversion error
 +-- ===================================================================
 +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int;
 +SELECT * FROM ft1 WHERE c1 = 1;  -- ERROR
 +ERROR:  invalid input syntax for integer: "foo"
 +CONTEXT:  column "c8" of foreign table "ft1"
 +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
 +-- ===================================================================
 +-- subtransaction
 +--  + local/remote error doesn't break cursor
 +-- ===================================================================
 +BEGIN;
 +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1;
 +FETCH c;
 + c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
 +----+----+-------+------------------------------+--------------------------+----+------------+-----
 +  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
 +(1 row)
 +
 +SAVEPOINT s;
 +ERROR OUT;          -- ERROR
 +ERROR:  syntax error at or near "ERROR"
 +LINE 1: ERROR OUT;
 +        ^
 +ROLLBACK TO s;
 +FETCH c;
 + c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
 +----+----+-------+------------------------------+--------------------------+----+------------+-----
 +  2 |  2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2  | 2          | foo
 +(1 row)
 +
 +SAVEPOINT s;
 +SELECT * FROM ft1 WHERE 1 / (c1 - 1) > 0;  -- ERROR
 +ERROR:  division by zero
 +CONTEXT:  Remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((1 OPERATOR(pg_catalog./) ("C 1" OPERATOR(pg_catalog.-) 1)) OPERATOR(pg_catalog.>) 0))
 +ROLLBACK TO s;
 +FETCH c;
 + c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
 +----+----+-------+------------------------------+--------------------------+----+------------+-----
 +  3 |  3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3  | 3          | foo
 +(1 row)
 +
 +SELECT * FROM ft1 ORDER BY c1 LIMIT 1;
 + c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
 +----+----+-------+------------------------------+--------------------------+----+------------+-----
 +  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
 +(1 row)
 +
 +COMMIT;
      --- /dev/null
   +/*-------------------------------------------------------------------------
 + *
 + * 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;
 +}
      --- /dev/null
   +/* 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;
      --- /dev/null
   +/*-------------------------------------------------------------------------
 + *
 + * postgres_fdw.c
 + *       Foreign-data wrapper for remote PostgreSQL servers
 + *
 + * Portions Copyright (c) 2012-2013, PostgreSQL Global Development Group
 + *
 + * IDENTIFICATION
 + *       contrib/postgres_fdw/postgres_fdw.c
 + *
 + *-------------------------------------------------------------------------
 + */
 +#include "postgres.h"
 +
 +#include "postgres_fdw.h"
 +
 +#include "access/htup_details.h"
 +#include "commands/defrem.h"
 +#include "commands/explain.h"
 +#include "commands/vacuum.h"
 +#include "foreign/fdwapi.h"
 +#include "funcapi.h"
 +#include "miscadmin.h"
 +#include "optimizer/cost.h"
 +#include "optimizer/pathnode.h"
 +#include "optimizer/planmain.h"
 +#include "parser/parsetree.h"
 +#include "utils/lsyscache.h"
 +#include "utils/memutils.h"
 +
 +
 +PG_MODULE_MAGIC;
 +
 +/* Default CPU cost to start up a foreign query. */
 +#define DEFAULT_FDW_STARTUP_COST   100.0
 +
 +/* Default CPU cost to process 1 row (above and beyond cpu_tuple_cost). */
 +#define DEFAULT_FDW_TUPLE_COST     0.01
 +
 +/*
 + * FDW-specific planner information kept in RelOptInfo.fdw_private for a
 + * foreign table.  This information is collected by postgresGetForeignRelSize.
 + */
 +typedef struct PgFdwRelationInfo
 +{
 +   /* XXX underdocumented, but a lot of this shouldn't be here anyway */
 +   StringInfoData sql;
 +   Cost        startup_cost;
 +   Cost        total_cost;
 +   List       *remote_conds;
 +   List       *param_conds;
 +   List       *local_conds;
 +   List       *param_numbers;
 +
 +   /* Cached catalog information. */
 +   ForeignTable *table;
 +   ForeignServer *server;
 +} PgFdwRelationInfo;
 +
 +/*
 + * Indexes of FDW-private information stored in fdw_private list.
 + *
 + * We store various information in ForeignScan.fdw_private to pass it from
 + * planner to executor.  Specifically there is:
 + *
 + * 1) SELECT statement text to be sent to the remote server
 + * 2) IDs of PARAM_EXEC Params used in the SELECT statement
 + *
 + * These items are indexed with the enum FdwPrivateIndex, so an item can be
 + * fetched with list_nth().  For example, to get the SELECT statement:
 + *     sql = strVal(list_nth(fdw_private, FdwPrivateSelectSql));
 + */
 +enum FdwPrivateIndex
 +{
 +   /* SQL statement to execute remotely (as a String node) */
 +   FdwPrivateSelectSql,
 +
 +   /* Integer list of param IDs of PARAM_EXEC Params used in SQL stmt */
 +   FdwPrivateExternParamIds,
 +
 +   /* # of elements stored in the list fdw_private */
 +   FdwPrivateNum
 +};
 +
 +/*
 + * Execution state of a foreign scan using postgres_fdw.
 + */
 +typedef struct PgFdwExecutionState
 +{
 +   Relation    rel;            /* relcache entry for the foreign table */
 +   AttInMetadata *attinmeta;   /* attribute datatype conversion metadata */
 +
 +   List       *fdw_private;    /* FDW-private information from planner */
 +
 +   /* for remote query execution */
 +   PGconn     *conn;           /* connection for the scan */
 +   unsigned int cursor_number; /* quasi-unique ID for my cursor */
 +   bool        cursor_exists;  /* have we created the cursor? */
 +   bool        extparams_done; /* have we converted PARAM_EXTERN params? */
 +   int         numParams;      /* number of parameters passed to query */
 +   Oid        *param_types;    /* array of types of query parameters */
 +   const char **param_values;  /* array of values of query parameters */
 +
 +   /* for storing result tuples */
 +   HeapTuple  *tuples;         /* array of currently-retrieved tuples */
 +   int         num_tuples;     /* # of tuples in array */
 +   int         next_tuple;     /* index of next one to return */
 +
 +   /* batch-level state, for optimizing rewinds and avoiding useless fetch */
 +   int         fetch_ct_2;     /* Min(# of fetches done, 2) */
 +   bool        eof_reached;    /* true if last fetch reached EOF */
 +
 +   /* working memory contexts */
 +   MemoryContext batch_cxt;    /* context holding current batch of tuples */
 +   MemoryContext temp_cxt;     /* context for per-tuple temporary data */
 +} PgFdwExecutionState;
 +
 +/*
 + * Workspace for analyzing a foreign table.
 + */
 +typedef struct PgFdwAnalyzeState
 +{
 +   Relation    rel;            /* relcache entry for the foreign table */
 +   AttInMetadata *attinmeta;   /* attribute datatype conversion metadata */
 +
 +   /* collected sample rows */
 +   HeapTuple  *rows;           /* array of size targrows */
 +   int         targrows;       /* target # of sample rows */
 +   int         numrows;        /* # of sample rows collected */
 +
 +   /* for random sampling */
 +   double      samplerows;     /* # of rows fetched */
 +   double      rowstoskip;     /* # of rows to skip before next sample */
 +   double      rstate;         /* random state */
 +
 +   /* working memory contexts */
 +   MemoryContext anl_cxt;      /* context for per-analyze lifespan data */
 +   MemoryContext temp_cxt;     /* context for per-tuple temporary data */
 +} PgFdwAnalyzeState;
 +
 +/*
 + * Identify the attribute where data conversion fails.
 + */
 +typedef struct ConversionLocation
 +{
 +   Relation    rel;            /* foreign table's relcache entry */
 +   AttrNumber  cur_attno;      /* attribute number being processed, or 0 */
 +} ConversionLocation;
 +
 +/*
 + * SQL functions
 + */
 +extern Datum postgres_fdw_handler(PG_FUNCTION_ARGS);
 +
 +PG_FUNCTION_INFO_V1(postgres_fdw_handler);
 +
 +/*
 + * FDW callback routines
 + */
 +static void postgresGetForeignRelSize(PlannerInfo *root,
 +                         RelOptInfo *baserel,
 +                         Oid foreigntableid);
 +static void postgresGetForeignPaths(PlannerInfo *root,
 +                       RelOptInfo *baserel,
 +                       Oid foreigntableid);
 +static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
 +                      RelOptInfo *baserel,
 +                      Oid foreigntableid,
 +                      ForeignPath *best_path,
 +                      List *tlist,
 +                      List *scan_clauses);
 +static void postgresExplainForeignScan(ForeignScanState *node,
 +                          ExplainState *es);
 +static void postgresBeginForeignScan(ForeignScanState *node, int eflags);
 +static TupleTableSlot *postgresIterateForeignScan(ForeignScanState *node);
 +static void postgresReScanForeignScan(ForeignScanState *node);
 +static void postgresEndForeignScan(ForeignScanState *node);
 +static bool postgresAnalyzeForeignTable(Relation relation,
 +                           AcquireSampleRowsFunc *func,
 +                           BlockNumber *totalpages);
 +
 +/*
 + * Helper functions
 + */
 +static void get_remote_estimate(const char *sql,
 +                   PGconn *conn,
 +                   double *rows,
 +                   int *width,
 +                   Cost *startup_cost,
 +                   Cost *total_cost);
 +static void create_cursor(ForeignScanState *node);
 +static void fetch_more_data(ForeignScanState *node);
 +static void close_cursor(PGconn *conn, unsigned int cursor_number);
 +static int postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 +                             HeapTuple *rows, int targrows,
 +                             double *totalrows,
 +                             double *totaldeadrows);
 +static void analyze_row_processor(PGresult *res, int row,
 +                     PgFdwAnalyzeState *astate);
 +static HeapTuple make_tuple_from_result_row(PGresult *res,
 +                          int row,
 +                          Relation rel,
 +                          AttInMetadata *attinmeta,
 +                          MemoryContext temp_context);
 +static void conversion_error_callback(void *arg);
 +
 +
 +/*
 + * Foreign-data wrapper handler function: return a struct with pointers
 + * to my callback routines.
 + */
 +Datum
 +postgres_fdw_handler(PG_FUNCTION_ARGS)
 +{
 +   FdwRoutine *routine = makeNode(FdwRoutine);
 +
 +   /* Required handler functions. */
 +   routine->GetForeignRelSize = postgresGetForeignRelSize;
 +   routine->GetForeignPaths = postgresGetForeignPaths;
 +   routine->GetForeignPlan = postgresGetForeignPlan;
 +   routine->ExplainForeignScan = postgresExplainForeignScan;
 +   routine->BeginForeignScan = postgresBeginForeignScan;
 +   routine->IterateForeignScan = postgresIterateForeignScan;
 +   routine->ReScanForeignScan = postgresReScanForeignScan;
 +   routine->EndForeignScan = postgresEndForeignScan;
 +
 +   /* Optional handler functions. */
 +   routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
 +
 +   PG_RETURN_POINTER(routine);
 +}
 +
 +/*
 + * postgresGetForeignRelSize
 + *     Estimate # of rows and width of the result of the scan
 + *
 + * Here we estimate number of rows returned by the scan in two steps.  In the
 + * first step, we execute remote EXPLAIN command to obtain the number of rows
 + * returned from remote side.  In the second step, we calculate the selectivity
 + * of the filtering done on local side, and modify first estimate.
 + *
 + * We have to get some catalog objects and generate remote query string here,
 + * so we store such expensive information in FDW private area of RelOptInfo and
 + * pass them to subsequent functions for reuse.
 + */
 +static void
 +postgresGetForeignRelSize(PlannerInfo *root,
 +                         RelOptInfo *baserel,
 +                         Oid foreigntableid)
 +{
 +   bool        use_remote_explain = false;
 +   ListCell   *lc;
 +   PgFdwRelationInfo *fpinfo;
 +   StringInfo  sql;
 +   ForeignTable *table;
 +   ForeignServer *server;
 +   Selectivity sel;
 +   double      rows;
 +   int         width;
 +   Cost        startup_cost;
 +   Cost        total_cost;
 +   List       *remote_conds;
 +   List       *param_conds;
 +   List       *local_conds;
 +   List       *param_numbers;
 +
 +   /*
 +    * We use PgFdwRelationInfo to pass various information to subsequent
 +    * functions.
 +    */
 +   fpinfo = palloc0(sizeof(PgFdwRelationInfo));
 +   initStringInfo(&fpinfo->sql);
 +   sql = &fpinfo->sql;
 +
 +   /*
 +    * Determine whether we use remote estimate or not.  Note that per-table
 +    * setting overrides per-server setting.
 +    */
 +   table = GetForeignTable(foreigntableid);
 +   server = GetForeignServer(table->serverid);
 +   foreach(lc, server->options)
 +   {
 +       DefElem    *def = (DefElem *) lfirst(lc);
 +
 +       if (strcmp(def->defname, "use_remote_explain") == 0)
 +       {
 +           use_remote_explain = defGetBoolean(def);
 +           break;
 +       }
 +   }
 +   foreach(lc, table->options)
 +   {
 +       DefElem    *def = (DefElem *) lfirst(lc);
 +
 +       if (strcmp(def->defname, "use_remote_explain") == 0)
 +       {
 +           use_remote_explain = defGetBoolean(def);
 +           break;
 +       }
 +   }
 +
 +   /*
 +    * Construct remote query which consists of SELECT, FROM, and WHERE
 +    * clauses.  Conditions which contain any Param node are excluded because
 +    * placeholder can't be used in EXPLAIN statement.  Such conditions are
 +    * appended later.
 +    */
 +   classifyConditions(root, baserel, &remote_conds, ¶m_conds,
 +                      &local_conds, ¶m_numbers);
 +   deparseSimpleSql(sql, root, baserel, local_conds);
 +   if (list_length(remote_conds) > 0)
 +       appendWhereClause(sql, true, remote_conds, root);
 +
 +   /*
 +    * If the table or the server is configured to use remote EXPLAIN, connect
 +    * to the foreign server and execute EXPLAIN with the quals that don't
 +    * contain any Param nodes.  Otherwise, estimate rows using whatever
 +    * statistics we have locally, in a way similar to ordinary tables.
 +    */
 +   if (use_remote_explain)
 +   {
 +       RangeTblEntry *rte;
 +       Oid         userid;
 +       UserMapping *user;
 +       PGconn     *conn;
 +
 +       /*
 +        * Identify which user to do the remote access as.  This should match
 +        * what ExecCheckRTEPerms() does.  If we fail due to lack of
 +        * permissions, the query would have failed at runtime anyway.
 +        */
 +       rte = planner_rt_fetch(baserel->relid, root);
 +       userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 +
 +       user = GetUserMapping(userid, server->serverid);
 +       conn = GetConnection(server, user);
 +       get_remote_estimate(sql->data, conn, &rows, &width,
 +                           &startup_cost, &total_cost);
 +       ReleaseConnection(conn);
 +
 +       /*
 +        * Estimate selectivity of conditions which were not used in remote
 +        * EXPLAIN by calling clauselist_selectivity().  The best we can do
 +        * for these conditions is to estimate selectivity on the basis of
 +        * local statistics.
 +        */
 +       sel = clauselist_selectivity(root, param_conds,
 +                                    baserel->relid, JOIN_INNER, NULL);
 +       sel *= clauselist_selectivity(root, local_conds,
 +                                     baserel->relid, JOIN_INNER, NULL);
 +
 +       /* Report estimated numbers to planner. */
 +       baserel->rows = clamp_row_est(rows * sel);
 +       baserel->width = width;
 +   }
 +   else
 +   {
 +       /*
 +        * Estimate rows from the result of the last ANALYZE, using all
 +        * conditions specified in original query.
 +        *
 +        * If the foreign table has never been ANALYZEd, it will have relpages
 +        * and reltuples equal to zero, which most likely has nothing to do
 +        * with reality.  We can't do a whole lot about that if we're not
 +        * allowed to consult the remote server, but we can use a hack similar
 +        * to plancat.c's treatment of empty relations: use a minimum size
 +        * estimate of 10 pages, and divide by the column-datatype-based width
 +        * estimate to get the corresponding number of tuples.
 +        */
 +       if (baserel->tuples <= 0)
 +           baserel->tuples =
 +               (10 * BLCKSZ) / (baserel->width + sizeof(HeapTupleHeaderData));
 +
 +       set_baserel_size_estimates(root, baserel);
 +
 +       /*
 +        * XXX need to do something here to calculate sane startup and total
 +        * cost estimates ... for the moment, we do this:
 +        */
 +       startup_cost = 0;
 +       total_cost = baserel->rows * cpu_tuple_cost;
 +   }
 +
 +   /*
 +    * Finish deparsing remote query by adding conditions which were unusable
 +    * in remote EXPLAIN since they contain Param nodes.
 +    */
 +   if (list_length(param_conds) > 0)
 +       appendWhereClause(sql, !(list_length(remote_conds) > 0), param_conds,
 +                         root);
 +
 +   /*
 +    * Store obtained information into FDW-private area of RelOptInfo so it's
 +    * available to subsequent functions.
 +    */
 +   fpinfo->startup_cost = startup_cost;
 +   fpinfo->total_cost = total_cost;
 +   fpinfo->remote_conds = remote_conds;
 +   fpinfo->param_conds = param_conds;
 +   fpinfo->local_conds = local_conds;
 +   fpinfo->param_numbers = param_numbers;
 +   fpinfo->table = table;
 +   fpinfo->server = server;
 +   baserel->fdw_private = (void *) fpinfo;
 +}
 +
 +/*
 + * postgresGetForeignPaths
 + *     Create possible scan paths for a scan on the foreign table
 + */
 +static void
 +postgresGetForeignPaths(PlannerInfo *root,
 +                       RelOptInfo *baserel,
 +                       Oid foreigntableid)
 +{
 +   PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
 +   ForeignPath *path;
 +   ListCell   *lc;
 +   double      fdw_startup_cost = DEFAULT_FDW_STARTUP_COST;
 +   double      fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST;
 +   Cost        startup_cost;
 +   Cost        total_cost;
 +   List       *fdw_private;
 +
 +   /*
 +    * Check for user override of fdw_startup_cost, fdw_tuple_cost values
 +    */
 +   foreach(lc, fpinfo->server->options)
 +   {
 +       DefElem    *d = (DefElem *) lfirst(lc);
 +
 +       if (strcmp(d->defname, "fdw_startup_cost") == 0)
 +           fdw_startup_cost = strtod(defGetString(d), NULL);
 +       else if (strcmp(d->defname, "fdw_tuple_cost") == 0)
 +           fdw_tuple_cost = strtod(defGetString(d), NULL);
 +   }
 +
 +   /*
 +    * We have cost values which are estimated on remote side, so adjust them
 +    * for better estimate which respect various stuffs to complete the scan,
 +    * such as sending query, transferring result, and local filtering.
 +    */
 +   startup_cost = fpinfo->startup_cost;
 +   total_cost = fpinfo->total_cost;
 +
 +   /*----------
 +    * Adjust costs with factors of the corresponding foreign server:
 +    *   - add cost to establish connection to both startup and total
 +    *   - add cost to manipulate on remote, and transfer result to total
 +    *   - add cost to manipulate tuples on local side to total
 +    *----------
 +    */
 +   startup_cost += fdw_startup_cost;
 +   total_cost += fdw_startup_cost;
 +   total_cost += fdw_tuple_cost * baserel->rows;
 +   total_cost += cpu_tuple_cost * baserel->rows;
 +
 +   /*
 +    * Build the fdw_private list that will be available to the executor.
 +    * Items in the list must match enum FdwPrivateIndex, above.
 +    */
 +   fdw_private = list_make2(makeString(fpinfo->sql.data),
 +                            fpinfo->param_numbers);
 +
 +   /*
 +    * Create simplest ForeignScan path node and add it to baserel.  This path
 +    * corresponds to SeqScan path of regular tables (though depending on what
 +    * baserestrict conditions we were able to send to remote, there might
 +    * actually be an indexscan happening there).
 +    */
 +   path = create_foreignscan_path(root, baserel,
 +                                  baserel->rows,
 +                                  startup_cost,
 +                                  total_cost,
 +                                  NIL, /* no pathkeys */
 +                                  NULL,        /* no outer rel either */
 +                                  fdw_private);
 +   add_path(baserel, (Path *) path);
 +
 +   /*
 +    * XXX We can consider sorted path or parameterized path here if we know
 +    * that foreign table is indexed on remote end.  For this purpose, we
 +    * might have to support FOREIGN INDEX to represent possible sets of sort
 +    * keys and/or filtering.  Or we could just try some join conditions and
 +    * see if remote side estimates using them as markedly cheaper.  Note that
 +    * executor functions need work to support internal Params before we can
 +    * try generating any parameterized paths, though.
 +    */
 +}
 +
 +/*
 + * postgresGetForeignPlan
 + *     Create ForeignScan plan node which implements selected best path
 + */
 +static ForeignScan *
 +postgresGetForeignPlan(PlannerInfo *root,
 +                      RelOptInfo *baserel,
 +                      Oid foreigntableid,
 +                      ForeignPath *best_path,
 +                      List *tlist,
 +                      List *scan_clauses)
 +{
 +   PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
 +   Index       scan_relid = baserel->relid;
 +   List       *fdw_private = best_path->fdw_private;
 +   List       *remote_exprs = NIL;
 +   List       *local_exprs = NIL;
 +   ListCell   *lc;
 +
 +   /*
 +    * Separate the scan_clauses into those that can be executed remotely and
 +    * those that can't.  For now, we accept only remote clauses that were
 +    * previously determined to be safe by classifyClauses (so, only
 +    * baserestrictinfo clauses can be used that way).
 +    *
 +    * This code must match "extract_actual_clauses(scan_clauses, false)"
 +    * except for the additional decision about remote versus local execution.
 +    */
 +   foreach(lc, scan_clauses)
 +   {
 +       RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
 +
 +       Assert(IsA(rinfo, RestrictInfo));
 +
 +       /* Ignore any pseudoconstants, they're dealt with elsewhere */
 +       if (rinfo->pseudoconstant)
 +           continue;
 +
 +       /* Either simple or parameterized remote clauses are OK now */
 +       if (list_member_ptr(fpinfo->remote_conds, rinfo) ||
 +           list_member_ptr(fpinfo->param_conds, rinfo))
 +           remote_exprs = lappend(remote_exprs, rinfo->clause);
 +       else
 +           local_exprs = lappend(local_exprs, rinfo->clause);
 +   }
 +
 +   /*
 +    * Create the ForeignScan node from target list, local filtering
 +    * expressions, remote filtering expressions, and FDW private information.
 +    *
 +    * Note that the remote_exprs are stored in the fdw_exprs field of the
 +    * finished plan node; we can't keep them in private state because then
 +    * they wouldn't be subject to later planner processing.
 +    *
 +    * XXX Currently, the remote_exprs aren't actually used at runtime, so we
 +    * don't need to store them at all.  But we'll keep this behavior for a
 +    * little while for debugging reasons.
 +    */
 +   return make_foreignscan(tlist,
 +                           local_exprs,
 +                           scan_relid,
 +                           remote_exprs,
 +                           fdw_private);
 +}
 +
 +/*
 + * postgresExplainForeignScan
 + *     Produce extra output for EXPLAIN
 + */
 +static void
 +postgresExplainForeignScan(ForeignScanState *node, ExplainState *es)
 +{
 +   List       *fdw_private;
 +   char       *sql;
 +
 +   if (es->verbose)
 +   {
 +       fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private;
 +       sql = strVal(list_nth(fdw_private, FdwPrivateSelectSql));
 +       ExplainPropertyText("Remote SQL", sql, es);
 +   }
 +}
 +
 +/*
 + * postgresBeginForeignScan
 + *     Initiate an executor scan of a foreign PostgreSQL table.
 + */
 +static void
 +postgresBeginForeignScan(ForeignScanState *node, int eflags)
 +{
 +   ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
 +   EState     *estate = node->ss.ps.state;
 +   PgFdwExecutionState *festate;
 +   RangeTblEntry *rte;
 +   Oid         userid;
 +   ForeignTable *table;
 +   ForeignServer *server;
 +   UserMapping *user;
 +   List       *param_numbers;
 +   int         numParams;
 +   int         i;
 +
 +   /*
 +    * Do nothing in EXPLAIN (no ANALYZE) case.  node->fdw_state stays NULL.
 +    */
 +   if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
 +       return;
 +
 +   /*
 +    * We'll save private state in node->fdw_state.
 +    */
 +   festate = (PgFdwExecutionState *) palloc0(sizeof(PgFdwExecutionState));
 +   node->fdw_state = (void *) festate;
 +
 +   /*
 +    * Identify which user to do the remote access as.  This should match what
 +    * ExecCheckRTEPerms() does.
 +    */
 +   rte = rt_fetch(fsplan->scan.scanrelid, estate->es_range_table);
 +   userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 +
 +   /* Get info about foreign table. */
 +   festate->rel = node->ss.ss_currentRelation;
 +   table = GetForeignTable(RelationGetRelid(festate->rel));
 +   server = GetForeignServer(table->serverid);
 +   user = GetUserMapping(userid, server->serverid);
 +
 +   /*
 +    * Get connection to the foreign server.  Connection manager will
 +    * establish new connection if necessary.
 +    */
 +   festate->conn = GetConnection(server, user);
 +
 +   /* Assign a unique ID for my cursor */
 +   festate->cursor_number = GetCursorNumber(festate->conn);
 +   festate->cursor_exists = false;
 +
 +   /* Get private info created by planner functions. */
 +   festate->fdw_private = fsplan->fdw_private;
 +
 +   /* Create contexts for batches of tuples and per-tuple temp workspace. */
 +   festate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt,
 +                                              "postgres_fdw tuple data",
 +                                              ALLOCSET_DEFAULT_MINSIZE,
 +                                              ALLOCSET_DEFAULT_INITSIZE,
 +                                              ALLOCSET_DEFAULT_MAXSIZE);
 +   festate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
 +                                             "postgres_fdw temporary data",
 +                                             ALLOCSET_SMALL_MINSIZE,
 +                                             ALLOCSET_SMALL_INITSIZE,
 +                                             ALLOCSET_SMALL_MAXSIZE);
 +
 +   /* Get info we'll need for data conversion. */
 +   festate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(festate->rel));
 +
 +   /*
 +    * Allocate buffer for query parameters, if the remote conditions use any.
 +    *
 +    * We use a parameter slot for each PARAM_EXTERN parameter, even though
 +    * not all of them may get sent to the remote server.  This allows us to
 +    * refer to Params by their original number rather than remapping, and it
 +    * doesn't cost much.  Slots that are not actually used get filled with
 +    * null values that are arbitrarily marked as being of type int4.
 +    */
 +   param_numbers = (List *)
 +       list_nth(festate->fdw_private, FdwPrivateExternParamIds);
 +   if (param_numbers != NIL)
 +   {
 +       ParamListInfo params = estate->es_param_list_info;
 +
 +       numParams = params ? params->numParams : 0;
 +   }
 +   else
 +       numParams = 0;
 +   festate->numParams = numParams;
 +   if (numParams > 0)
 +   {
 +       /* we initially fill all slots with value = NULL, type = int4 */
 +       festate->param_types = (Oid *) palloc(numParams * sizeof(Oid));
 +       festate->param_values = (const char **) palloc0(numParams * sizeof(char *));
 +       for (i = 0; i < numParams; i++)
 +           festate->param_types[i] = INT4OID;
 +   }
 +   else
 +   {
 +       festate->param_types = NULL;
 +       festate->param_values = NULL;
 +   }
 +   festate->extparams_done = false;
 +}
 +
 +/*
 + * postgresIterateForeignScan
 + *     Retrieve next row from the result set, or clear tuple slot to indicate
 + *     EOF.
 + */
 +static TupleTableSlot *
 +postgresIterateForeignScan(ForeignScanState *node)
 +{
 +   PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
 +   TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
 +
 +   /*
 +    * If this is the first call after Begin or ReScan, we need to create the
 +    * cursor on the remote side.
 +    */
 +   if (!festate->cursor_exists)
 +       create_cursor(node);
 +
 +   /*
 +    * Get some more tuples, if we've run out.
 +    */
 +   if (festate->next_tuple >= festate->num_tuples)
 +   {
 +       /* No point in another fetch if we already detected EOF, though. */
 +       if (!festate->eof_reached)
 +           fetch_more_data(node);
 +       /* If we didn't get any tuples, must be end of data. */
 +       if (festate->next_tuple >= festate->num_tuples)
 +           return ExecClearTuple(slot);
 +   }
 +
 +   /*
 +    * Return the next tuple.
 +    */
 +   ExecStoreTuple(festate->tuples[festate->next_tuple++],
 +                  slot,
 +                  InvalidBuffer,
 +                  false);
 +
 +   return slot;
 +}
 +
 +/*
 + * postgresReScanForeignScan
 + *     Restart the scan.
 + */
 +static void
 +postgresReScanForeignScan(ForeignScanState *node)
 +{
 +   PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
 +   char        sql[64];
 +   PGresult   *res;
 +
 +   /*
 +    * Note: we assume that PARAM_EXTERN params don't change over the life of
 +    * the query, so no need to reset extparams_done.
 +    */
 +
 +   /* If we haven't created the cursor yet, nothing to do. */
 +   if (!festate->cursor_exists)
 +       return;
 +
 +   /*
 +    * If any internal parameters affecting this node have changed, we'd
 +    * better destroy and recreate the cursor.  Otherwise, rewinding it should
 +    * be good enough.  If we've only fetched zero or one batch, we needn't
 +    * even rewind the cursor, just rescan what we have.
 +    */
 +   if (node->ss.ps.chgParam != NULL)
 +   {
 +       festate->cursor_exists = false;
 +       snprintf(sql, sizeof(sql), "CLOSE c%u",
 +                festate->cursor_number);
 +   }
 +   else if (festate->fetch_ct_2 > 1)
 +   {
 +       snprintf(sql, sizeof(sql), "MOVE BACKWARD ALL IN c%u",
 +                festate->cursor_number);
 +   }
 +   else
 +   {
 +       /* Easy: just rescan what we already have in memory, if anything */
 +       festate->next_tuple = 0;
 +       return;
 +   }
 +
 +   /*
 +    * We don't use a PG_TRY block here, so be careful not to throw error
 +    * without releasing the PGresult.
 +    */
 +   res = PQexec(festate->conn, sql);
 +   if (PQresultStatus(res) != PGRES_COMMAND_OK)
 +       pgfdw_report_error(ERROR, res, true, sql);
 +   PQclear(res);
 +
 +   /* Now force a fresh FETCH. */
 +   festate->tuples = NULL;
 +   festate->num_tuples = 0;
 +   festate->next_tuple = 0;
 +   festate->fetch_ct_2 = 0;
 +   festate->eof_reached = false;
 +}
 +
 +/*
 + * postgresEndForeignScan
 + *     Finish scanning foreign table and dispose objects used for this scan
 + */
 +static void
 +postgresEndForeignScan(ForeignScanState *node)
 +{
 +   PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
 +
 +   /* if festate is NULL, we are in EXPLAIN; nothing to do */
 +   if (festate == NULL)
 +       return;
 +
 +   /* Close the cursor if open, to prevent accumulation of cursors */
 +   if (festate->cursor_exists)
 +       close_cursor(festate->conn, festate->cursor_number);
 +
 +   /* Release remote connection */
 +   ReleaseConnection(festate->conn);
 +   festate->conn = NULL;
 +
 +   /* MemoryContexts will be deleted automatically. */
 +}
 +
 +/*
 + * Estimate costs of executing given SQL statement.
 + */
 +static void
 +get_remote_estimate(const char *sql, PGconn *conn,
 +                   double *rows, int *width,
 +                   Cost *startup_cost, Cost *total_cost)
 +{
 +   PGresult   *volatile res = NULL;
 +
 +   /* PGresult must be released before leaving this function. */
 +   PG_TRY();
 +   {
 +       StringInfoData buf;
 +       char       *line;
 +       char       *p;
 +       int         n;
 +
 +       /*
 +        * Execute EXPLAIN remotely on given SQL statement.
 +        */
 +       initStringInfo(&buf);
 +       appendStringInfo(&buf, "EXPLAIN %s", sql);
 +       res = PQexec(conn, buf.data);
 +       if (PQresultStatus(res) != PGRES_TUPLES_OK)
 +           pgfdw_report_error(ERROR, res, false, buf.data);
 +
 +       /*
 +        * Extract cost numbers for topmost plan node.  Note we search for a
 +        * left paren from the end of the line to avoid being confused by
 +        * other uses of parentheses.
 +        */
 +       line = PQgetvalue(res, 0, 0);
 +       p = strrchr(line, '(');
 +       if (p == NULL)
 +           elog(ERROR, "could not interpret EXPLAIN output: \"%s\"", line);
 +       n = sscanf(p, "(cost=%lf..%lf rows=%lf width=%d)",
 +                  startup_cost, total_cost, rows, width);
 +       if (n != 4)
 +           elog(ERROR, "could not interpret EXPLAIN output: \"%s\"", line);
 +
 +       PQclear(res);
 +       res = NULL;
 +   }
 +   PG_CATCH();
 +   {
 +       if (res)
 +           PQclear(res);
 +       PG_RE_THROW();
 +   }
 +   PG_END_TRY();
 +}
 +
 +/*
 + * Create cursor for node's query with current parameter values.
 + */
 +static void
 +create_cursor(ForeignScanState *node)
 +{
 +   PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
 +   int         numParams = festate->numParams;
 +   Oid        *types = festate->param_types;
 +   const char **values = festate->param_values;
 +   PGconn     *conn = festate->conn;
 +   char       *sql;
 +   StringInfoData buf;
 +   PGresult   *res;
 +
 +   /*
 +    * Construct array of external parameter values in text format.  Since
 +    * there might be random unconvertible stuff in the ParamExternData array,
 +    * take care to convert only values we actually need.
 +    *
 +    * Note that we leak the memory for the value strings until end of query;
 +    * this doesn't seem like a big problem, and in any case we might need to
 +    * recreate the cursor after a rescan, so we could need to re-use the
 +    * values anyway.
 +    */
 +   if (numParams > 0 && !festate->extparams_done)
 +   {
 +       ParamListInfo params = node->ss.ps.state->es_param_list_info;
 +       List       *param_numbers;
 +       ListCell   *lc;
 +
 +       param_numbers = (List *)
 +           list_nth(festate->fdw_private, FdwPrivateExternParamIds);
 +       foreach(lc, param_numbers)
 +       {
 +           int         paramno = lfirst_int(lc);
 +           ParamExternData *prm = ¶ms->params[paramno - 1];
 +
 +           /* give hook a chance in case parameter is dynamic */
 +           if (!OidIsValid(prm->ptype) && params->paramFetch != NULL)
 +               params->paramFetch(params, paramno);
 +
 +           /*
 +            * Get string representation of each parameter value by invoking
 +            * type-specific output function, unless the value is null.
 +            */
 +           types[paramno - 1] = prm->ptype;
 +           if (prm->isnull)
 +               values[paramno - 1] = NULL;
 +           else
 +           {
 +               Oid         out_func;
 +               bool        isvarlena;
 +
 +               getTypeOutputInfo(prm->ptype, &out_func, &isvarlena);
 +               values[paramno - 1] = OidOutputFunctionCall(out_func,
 +                                                           prm->value);
 +           }
 +       }
 +       festate->extparams_done = true;
 +   }
 +
 +   /* Construct the DECLARE CURSOR command */
 +   sql = strVal(list_nth(festate->fdw_private, FdwPrivateSelectSql));
 +   initStringInfo(&buf);
 +   appendStringInfo(&buf, "DECLARE c%u CURSOR FOR\n%s",
 +                    festate->cursor_number, sql);
 +
 +   /*
 +    * We don't use a PG_TRY block here, so be careful not to throw error
 +    * without releasing the PGresult.
 +    */
 +   res = PQexecParams(conn, buf.data, numParams, types, values,
 +                      NULL, NULL, 0);
 +   if (PQresultStatus(res) != PGRES_COMMAND_OK)
 +       pgfdw_report_error(ERROR, res, true, sql);
 +   PQclear(res);
 +
 +   /* Mark the cursor as created, and show no tuples have been retrieved */
 +   festate->cursor_exists = true;
 +   festate->tuples = NULL;
 +   festate->num_tuples = 0;
 +   festate->next_tuple = 0;
 +   festate->fetch_ct_2 = 0;
 +   festate->eof_reached = false;
 +
 +   /* Clean up */
 +   pfree(buf.data);
 +}
 +
 +/*
 + * Fetch some more rows from the node's cursor.
 + */
 +static void
 +fetch_more_data(ForeignScanState *node)
 +{
 +   PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
 +   PGresult   *volatile res = NULL;
 +   MemoryContext oldcontext;
 +
 +   /*
 +    * We'll store the tuples in the batch_cxt.  First, flush the previous
 +    * batch.
 +    */
 +   festate->tuples = NULL;
 +   MemoryContextReset(festate->batch_cxt);
 +   oldcontext = MemoryContextSwitchTo(festate->batch_cxt);
 +
 +   /* PGresult must be released before leaving this function. */
 +   PG_TRY();
 +   {
 +       PGconn     *conn = festate->conn;
 +       char        sql[64];
 +       int         fetch_size;
 +       int         numrows;
 +       int         i;
 +
 +       /* The fetch size is arbitrary, but shouldn't be enormous. */
 +       fetch_size = 100;
 +
 +       snprintf(sql, sizeof(sql), "FETCH %d FROM c%u",
 +                fetch_size, festate->cursor_number);
 +
 +       res = PQexec(conn, sql);
 +       /* On error, report the original query, not the FETCH. */
 +       if (PQresultStatus(res) != PGRES_TUPLES_OK)
 +           pgfdw_report_error(ERROR, res, false,
 +                              strVal(list_nth(festate->fdw_private,
 +                                              FdwPrivateSelectSql)));
 +
 +       /* Convert the data into HeapTuples */
 +       numrows = PQntuples(res);
 +       festate->tuples = (HeapTuple *) palloc0(numrows * sizeof(HeapTuple));
 +       festate->num_tuples = numrows;
 +       festate->next_tuple = 0;
 +
 +       for (i = 0; i < numrows; i++)
 +       {
 +           festate->tuples[i] =
 +               make_tuple_from_result_row(res, i,
 +                                          festate->rel,
 +                                          festate->attinmeta,
 +                                          festate->temp_cxt);
 +       }
 +
 +       /* Update fetch_ct_2 */
 +       if (festate->fetch_ct_2 < 2)
 +           festate->fetch_ct_2++;
 +
 +       /* Must be EOF if we didn't get as many tuples as we asked for. */
 +       festate->eof_reached = (numrows < fetch_size);
 +
 +       PQclear(res);
 +       res = NULL;
 +   }
 +   PG_CATCH();
 +   {
 +       if (res)
 +           PQclear(res);
 +       PG_RE_THROW();
 +   }
 +   PG_END_TRY();
 +
 +   MemoryContextSwitchTo(oldcontext);
 +}
 +
 +/*
 + * Utility routine to close a cursor.
 + */
 +static void
 +close_cursor(PGconn *conn, unsigned int cursor_number)
 +{
 +   char        sql[64];
 +   PGresult   *res;
 +
 +   snprintf(sql, sizeof(sql), "CLOSE c%u", cursor_number);
 +
 +   /*
 +    * We don't use a PG_TRY block here, so be careful not to throw error
 +    * without releasing the PGresult.
 +    */
 +   res = PQexec(conn, sql);
 +   if (PQresultStatus(res) != PGRES_COMMAND_OK)
 +       pgfdw_report_error(ERROR, res, true, sql);
 +   PQclear(res);
 +}
 +
 +/*
 + * postgresAnalyzeForeignTable
 + *     Test whether analyzing this foreign table is supported
 + */
 +static bool
 +postgresAnalyzeForeignTable(Relation relation,
 +                           AcquireSampleRowsFunc *func,
 +                           BlockNumber *totalpages)
 +{
 +   *totalpages = 0;            /* XXX this is probably a bad idea */
 +   *func = postgresAcquireSampleRowsFunc;
 +
 +   return true;
 +}
 +
 +/*
 + * Acquire a random sample of rows from foreign table managed by postgres_fdw.
 + *
 + * We fetch the whole table from the remote side and pick out some sample rows.
 + *
 + * Selected rows are returned in the caller-allocated array rows[],
 + * which must have at least targrows entries.
 + * The actual number of rows selected is returned as the function result.
 + * We also count the total number of rows in the table and return it into
 + * *totalrows. Note that *totaldeadrows is always set to 0.
 + *
 + * Note that the returned list of rows is not always in order by physical
 + * position in the table.  Therefore, correlation estimates derived later
 + * may be meaningless, but it's OK because we don't use the estimates
 + * currently (the planner only pays attention to correlation for indexscans).
 + */
 +static int
 +postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 +                             HeapTuple *rows, int targrows,
 +                             double *totalrows,
 +                             double *totaldeadrows)
 +{
 +   PgFdwAnalyzeState astate;
 +   ForeignTable *table;
 +   ForeignServer *server;
 +   UserMapping *user;
 +   PGconn     *conn;
 +   unsigned int cursor_number;
 +   StringInfoData sql;
 +   PGresult   *volatile res = NULL;
 +
 +   /* Initialize workspace state */
 +   astate.rel = relation;
 +   astate.attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(relation));
 +
 +   astate.rows = rows;
 +   astate.targrows = targrows;
 +   astate.numrows = 0;
 +   astate.samplerows = 0;
 +   astate.rowstoskip = -1;     /* -1 means not set yet */
 +   astate.rstate = anl_init_selection_state(targrows);
 +
 +   /* Remember ANALYZE context, and create a per-tuple temp context */
 +   astate.anl_cxt = CurrentMemoryContext;
 +   astate.temp_cxt = AllocSetContextCreate(CurrentMemoryContext,
 +                                           "postgres_fdw temporary data",
 +                                           ALLOCSET_SMALL_MINSIZE,
 +                                           ALLOCSET_SMALL_INITSIZE,
 +                                           ALLOCSET_SMALL_MAXSIZE);
 +
 +   /*
 +    * Get the connection to use.  We do the remote access as the table's
 +    * owner, even if the ANALYZE was started by some other user.
 +    */
 +   table = GetForeignTable(RelationGetRelid(relation));
 +   server = GetForeignServer(table->serverid);
 +   user = GetUserMapping(relation->rd_rel->relowner, server->serverid);
 +   conn = GetConnection(server, user);
 +
 +   /*
 +    * Construct cursor that retrieves whole rows from remote.
 +    */
 +   cursor_number = GetCursorNumber(conn);
 +   initStringInfo(&sql);
 +   appendStringInfo(&sql, "DECLARE c%u CURSOR FOR ", cursor_number);
 +   deparseAnalyzeSql(&sql, relation);
 +
 +   /* In what follows, do not risk leaking any PGresults. */
 +   PG_TRY();
 +   {
 +       res = PQexec(conn, sql.data);
 +       if (PQresultStatus(res) != PGRES_COMMAND_OK)
 +           pgfdw_report_error(ERROR, res, false, sql.data);
 +       PQclear(res);
 +       res = NULL;
 +
 +       /* Retrieve and process rows a batch at a time. */
 +       for (;;)
 +       {
 +           char        fetch_sql[64];
 +           int         fetch_size;
 +           int         numrows;
 +           int         i;
 +
 +           /* Allow users to cancel long query */
 +           CHECK_FOR_INTERRUPTS();
 +
 +           /*
 +            * XXX possible future improvement: if rowstoskip is large, we
 +            * could issue a MOVE rather than physically fetching the rows,
 +            * then just adjust rowstoskip and samplerows appropriately.
 +            */
 +
 +           /* The fetch size is arbitrary, but shouldn't be enormous. */
 +           fetch_size = 100;
 +
 +           /* Fetch some rows */
 +           snprintf(fetch_sql, sizeof(fetch_sql), "FETCH %d FROM c%u",
 +                    fetch_size, cursor_number);
 +
 +           res = PQexec(conn, fetch_sql);
 +           /* On error, report the original query, not the FETCH. */
 +           if (PQresultStatus(res) != PGRES_TUPLES_OK)
 +               pgfdw_report_error(ERROR, res, false, sql.data);
 +
 +           /* Process whatever we got. */
 +           numrows = PQntuples(res);
 +           for (i = 0; i < numrows; i++)
 +               analyze_row_processor(res, i, &astate);
 +
 +           PQclear(res);
 +           res = NULL;
 +
 +           /* Must be EOF if we didn't get all the rows requested. */
 +           if (numrows < fetch_size)
 +               break;
 +       }
 +
 +       /* Close the cursor, just to be tidy. */
 +       close_cursor(conn, cursor_number);
 +   }
 +   PG_CATCH();
 +   {
 +       if (res)
 +           PQclear(res);
 +       PG_RE_THROW();
 +   }
 +   PG_END_TRY();
 +
 +   ReleaseConnection(conn);
 +
 +   /* We assume that we have no dead tuple. */
 +   *totaldeadrows = 0.0;
 +
 +   /* We've retrieved all living tuples from foreign server. */
 +   *totalrows = astate.samplerows;
 +
 +   /*
 +    * Emit some interesting relation info
 +    */
 +   ereport(elevel,
 +           (errmsg("\"%s\": table contains %.0f rows, %d rows in sample",
 +                   RelationGetRelationName(relation),
 +                   astate.samplerows, astate.numrows)));
 +
 +   return astate.numrows;
 +}
 +
 +/*
 + * Collect sample rows from the result of query.
 + *  - Use all tuples in sample until target # of samples are collected.
 + *  - Subsequently, replace already-sampled tuples randomly.
 + */
 +static void
 +analyze_row_processor(PGresult *res, int row, PgFdwAnalyzeState *astate)
 +{
 +   int         targrows = astate->targrows;
 +   int         pos;            /* array index to store tuple in */
 +   MemoryContext oldcontext;
 +
 +   /* Always increment sample row counter. */
 +   astate->samplerows += 1;
 +
 +   /*
 +    * Determine the slot where this sample row should be stored.  Set pos to
 +    * negative value to indicate the row should be skipped.
 +    */
 +   if (astate->numrows < targrows)
 +   {
 +       /* First targrows rows are always included into the sample */
 +       pos = astate->numrows++;
 +   }
 +   else
 +   {
 +       /*
 +        * Now we start replacing tuples in the sample until we reach the end
 +        * of the relation.  Same algorithm as in acquire_sample_rows in
 +        * analyze.c; see Jeff Vitter's paper.
 +        */
 +       if (astate->rowstoskip < 0)
 +           astate->rowstoskip = anl_get_next_S(astate->samplerows, targrows,
 +                                               &astate->rstate);
 +
 +       if (astate->rowstoskip <= 0)
 +       {
 +           /* Choose a random reservoir element to replace. */
 +           pos = (int) (targrows * anl_random_fract());
 +           Assert(pos >= 0 && pos < targrows);
 +           heap_freetuple(astate->rows[pos]);
 +       }
 +       else
 +       {
 +           /* Skip this tuple. */
 +           pos = -1;
 +       }
 +
 +       astate->rowstoskip -= 1;
 +   }
 +
 +   if (pos >= 0)
 +   {
 +       /*
 +        * Create sample tuple from current result row, and store it in the
 +        * position determined above.  The tuple has to be created in anl_cxt.
 +        */
 +       oldcontext = MemoryContextSwitchTo(astate->anl_cxt);
 +
 +       astate->rows[pos] = make_tuple_from_result_row(res, row,
 +                                                      astate->rel,
 +                                                      astate->attinmeta,
 +                                                      astate->temp_cxt);
 +
 +       MemoryContextSwitchTo(oldcontext);
 +   }
 +}
 +
 +/*
 + * Create a tuple from the specified row of the PGresult.
 + *
 + * rel is the local representation of the foreign table, attinmeta is
 + * conversion data for the rel's tupdesc, and temp_context is a working
 + * context that can be reset after each tuple.
 + */
 +static HeapTuple
 +make_tuple_from_result_row(PGresult *res,
 +                          int row,
 +                          Relation rel,
 +                          AttInMetadata *attinmeta,
 +                          MemoryContext temp_context)
 +{
 +   HeapTuple   tuple;
 +   TupleDesc   tupdesc = RelationGetDescr(rel);
 +   Form_pg_attribute *attrs = tupdesc->attrs;
 +   Datum      *values;
 +   bool       *nulls;
 +   ConversionLocation errpos;
 +   ErrorContextCallback errcallback;
 +   MemoryContext oldcontext;
 +   int         i;
 +   int         j;
 +
 +   Assert(row < PQntuples(res));
 +
 +   /*
 +    * Do the following work in a temp context that we reset after each tuple.
 +    * This cleans up not only the data we have direct access to, but any
 +    * cruft the I/O functions might leak.
 +    */
 +   oldcontext = MemoryContextSwitchTo(temp_context);
 +
 +   values = (Datum *) palloc(tupdesc->natts * sizeof(Datum));
 +   nulls = (bool *) palloc(tupdesc->natts * sizeof(bool));
 +
 +   /*
 +    * Set up and install callback to report where conversion error occurs.
 +    */
 +   errpos.rel = rel;
 +   errpos.cur_attno = 0;
 +   errcallback.callback = conversion_error_callback;
 +   errcallback.arg = (void *) &errpos;
 +   errcallback.previous = error_context_stack;
 +   error_context_stack = &errcallback;
 +
 +   /*
 +    * i indexes columns in the relation, j indexes columns in the PGresult.
 +    * We assume dropped columns are not represented in the PGresult.
 +    */
 +   for (i = 0, j = 0; i < tupdesc->natts; i++)
 +   {
 +       char       *valstr;
 +
 +       /* skip dropped columns. */
 +       if (attrs[i]->attisdropped)
 +       {
 +           values[i] = (Datum) 0;
 +           nulls[i] = true;
 +           continue;
 +       }
 +
 +       /* convert value to internal representation */
 +       if (PQgetisnull(res, row, j))
 +       {
 +           valstr = NULL;
 +           nulls[i] = true;
 +       }
 +       else
 +       {
 +           valstr = PQgetvalue(res, row, j);
 +           nulls[i] = false;
 +       }
 +
 +       /* Note: apply the input function even to nulls, to support domains */
 +       errpos.cur_attno = i + 1;
 +       values[i] = InputFunctionCall(&attinmeta->attinfuncs[i],
 +                                     valstr,
 +                                     attinmeta->attioparams[i],
 +                                     attinmeta->atttypmods[i]);
 +       errpos.cur_attno = 0;
 +
 +       j++;
 +   }
 +
 +   /* Uninstall error context callback. */
 +   error_context_stack = errcallback.previous;
 +
 +   /* check result and tuple descriptor have the same number of columns */
 +   if (j != PQnfields(res))
 +       elog(ERROR, "remote query result does not match the foreign table");
 +
 +   /*
 +    * Build the result tuple in caller's memory context.
 +    */
 +   MemoryContextSwitchTo(oldcontext);
 +
 +   tuple = heap_form_tuple(tupdesc, values, nulls);
 +
 +   /* Clean up */
 +   MemoryContextReset(temp_context);
 +
 +   return tuple;
 +}
 +
 +/*
 + * Callback function which is called when error occurs during column value
 + * conversion. Print names of column and relation.
 + */
 +static void
 +conversion_error_callback(void *arg)
 +{
 +   ConversionLocation *errpos = (ConversionLocation *) arg;
 +   TupleDesc   tupdesc = RelationGetDescr(errpos->rel);
 +
 +   if (errpos->cur_attno > 0 && errpos->cur_attno <= tupdesc->natts)
 +       errcontext("column \"%s\" of foreign table \"%s\"",
 +                  NameStr(tupdesc->attrs[errpos->cur_attno - 1]->attname),
 +                  RelationGetRelationName(errpos->rel));
 +}
      --- /dev/null
   +# postgres_fdw extension
 +comment = 'foreign-data wrapper for remote PostgreSQL servers'
 +default_version = '1.0'
 +module_pathname = '$libdir/postgres_fdw'
 +relocatable = true
      --- /dev/null
   +/*-------------------------------------------------------------------------
 + *
 + * 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 */
      --- /dev/null
   +-- ===================================================================
 +-- create FDW objects
 +-- ===================================================================
 +
 +CREATE EXTENSION postgres_fdw;
 +
 +CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw;
 +CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw
 +  OPTIONS (dbname 'contrib_regression');
 +
 +CREATE USER MAPPING FOR public SERVER testserver1
 +   OPTIONS (user 'value', password 'value');
 +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
 +
 +-- ===================================================================
 +-- create objects used through FDW loopback server
 +-- ===================================================================
 +CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz');
 +CREATE SCHEMA "S 1";
 +CREATE TABLE "S 1"."T 1" (
 +   "C 1" int NOT NULL,
 +   c2 int NOT NULL,
 +   c3 text,
 +   c4 timestamptz,
 +   c5 timestamp,
 +   c6 varchar(10),
 +   c7 char(10),
 +   c8 user_enum,
 +   CONSTRAINT t1_pkey PRIMARY KEY ("C 1")
 +);
 +CREATE TABLE "S 1"."T 2" (
 +   c1 int NOT NULL,
 +   c2 text,
 +   CONSTRAINT t2_pkey PRIMARY KEY (c1)
 +);
 +
 +INSERT INTO "S 1"."T 1"
 +   SELECT id,
 +          id % 10,
 +          to_char(id, 'FM00000'),
 +          '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval,
 +          '1970-01-01'::timestamp + ((id % 100) || ' days')::interval,
 +          id % 10,
 +          id % 10,
 +          'foo'::user_enum
 +   FROM generate_series(1, 1000) id;
 +INSERT INTO "S 1"."T 2"
 +   SELECT id,
 +          'AAA' || to_char(id, 'FM000')
 +   FROM generate_series(1, 100) id;
 +
 +ANALYZE "S 1"."T 1";
 +ANALYZE "S 1"."T 2";
 +
 +-- ===================================================================
 +-- create foreign tables
 +-- ===================================================================
 +CREATE FOREIGN TABLE ft1 (
 +   c0 int,
 +   c1 int NOT NULL,
 +   c2 int NOT NULL,
 +   c3 text,
 +   c4 timestamptz,
 +   c5 timestamp,
 +   c6 varchar(10),
 +   c7 char(10),
 +   c8 user_enum
 +) SERVER loopback;
 +ALTER FOREIGN TABLE ft1 DROP COLUMN c0;
 +
 +CREATE FOREIGN TABLE ft2 (
 +   c0 int,
 +   c1 int NOT NULL,
 +   c2 int NOT NULL,
 +   c3 text,
 +   c4 timestamptz,
 +   c5 timestamp,
 +   c6 varchar(10),
 +   c7 char(10),
 +   c8 user_enum
 +) SERVER loopback;
 +ALTER FOREIGN TABLE ft2 DROP COLUMN c0;
 +
 +-- ===================================================================
 +-- tests for validator
 +-- ===================================================================
 +-- requiressl, krbsrvname and gsslib are omitted because they depend on
 +-- configure options
 +ALTER SERVER testserver1 OPTIONS (
 +   use_remote_explain 'false',
 +   fdw_startup_cost '123.456',
 +   fdw_tuple_cost '0.123',
 +   service 'value',
 +   connect_timeout 'value',
 +   dbname 'value',
 +   host 'value',
 +   hostaddr 'value',
 +   port 'value',
 +   --client_encoding 'value',
 +   application_name 'value',
 +   --fallback_application_name 'value',
 +   keepalives 'value',
 +   keepalives_idle 'value',
 +   keepalives_interval 'value',
 +   -- requiressl 'value',
 +   sslcompression 'value',
 +   sslmode 'value',
 +   sslcert 'value',
 +   sslkey 'value',
 +   sslrootcert 'value',
 +   sslcrl 'value'
 +   --requirepeer 'value',
 +   -- krbsrvname 'value',
 +   -- gsslib 'value',
 +   --replication 'value'
 +);
 +ALTER USER MAPPING FOR public SERVER testserver1
 +   OPTIONS (DROP user, DROP password);
 +ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1');
 +ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
 +ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
 +ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
 +\det+
 +
 +-- Now we should be able to run ANALYZE.
 +-- To exercise multiple code paths, we use local stats on ft1
 +-- and remote_explain mode on ft2.
 +ANALYZE ft1;
 +ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_explain 'true');
 +
 +-- ===================================================================
 +-- simple queries
 +-- ===================================================================
 +-- single table, with/without alias
 +EXPLAIN (COSTS false) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
 +SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
 +SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
 +-- empty result
 +SELECT * FROM ft1 WHERE false;
 +-- with WHERE clause
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
 +SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
 +-- aggregate
 +SELECT COUNT(*) FROM ft1 t1;
 +-- join two tables
 +SELECT t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
 +-- subquery
 +SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1;
 +-- subquery+MAX
 +SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1;
 +-- used in CTE
 +WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1;
 +-- fixed values
 +SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1;
 +-- user-defined operator/function
 +CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$
 +BEGIN
 +RETURN abs($1);
 +END
 +$$ LANGUAGE plpgsql IMMUTABLE;
 +CREATE OPERATOR === (
 +    LEFTARG = int,
 +    RIGHTARG = int,
 +    PROCEDURE = int4eq,
 +    COMMUTATOR = ===,
 +    NEGATOR = !==
 +);
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2);
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2;
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
 +
 +-- ===================================================================
 +-- WHERE with remotely-executable conditions
 +-- ===================================================================
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 1;         -- Var, OpExpr(b), Const
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NULL;        -- NullTest
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL;    -- NullTest
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = -c1;          -- OpExpr(l)
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE 1 = c1!;           -- OpExpr(r)
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar';  -- check special chars
 +EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c8 = 'foo';  -- can't be sent to remote
 +
 +-- ===================================================================
 +-- parameterized queries
 +-- ===================================================================
 +-- simple join
 +PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st1(1, 2);
 +EXECUTE st1(1, 1);
 +EXECUTE st1(101, 101);
 +-- subquery using stable function (can't be sent to remote)
 +PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c4) = 6) ORDER BY c1;
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20);
 +EXECUTE st2(10, 20);
 +EXECUTE st1(101, 101);
 +-- subquery using immutable function (can be sent to remote)
 +PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c5) = 6) ORDER BY c1;
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20);
 +EXECUTE st3(10, 20);
 +EXECUTE st3(20, 30);
 +-- custom plan should be chosen initially
 +PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1;
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
 +-- once we try it enough times, should switch to generic plan
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
 +-- value of $1 should not be sent to remote
 +PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2;
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
 +EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
 +EXECUTE st5('foo', 1);
 +
 +-- cleanup
 +DEALLOCATE st1;
 +DEALLOCATE st2;
 +DEALLOCATE st3;
 +DEALLOCATE st4;
 +DEALLOCATE st5;
 +
 +-- ===================================================================
 +-- used in pl/pgsql function
 +-- ===================================================================
 +CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$
 +DECLARE
 +   v_c1 int;
 +BEGIN
 +    SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1;
 +    PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1;
 +    RETURN v_c1;
 +END;
 +$$ LANGUAGE plpgsql;
 +SELECT f_test(100);
 +DROP FUNCTION f_test(int);
 +
 +-- ===================================================================
 +-- conversion error
 +-- ===================================================================
 +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int;
 +SELECT * FROM ft1 WHERE c1 = 1;  -- ERROR
 +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
 +
 +-- ===================================================================
 +-- subtransaction
 +--  + local/remote error doesn't break cursor
 +-- ===================================================================
 +BEGIN;
 +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1;
 +FETCH c;
 +SAVEPOINT s;
 +ERROR OUT;          -- ERROR
 +ROLLBACK TO s;
 +FETCH c;
 +SAVEPOINT s;
 +SELECT * FROM ft1 WHERE 1 / (c1 - 1) > 0;  -- ERROR
 +ROLLBACK TO s;
 +FETCH c;
 +SELECT * FROM ft1 ORDER BY c1 LIMIT 1;
 +COMMIT;
              <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,
           &pgstatstatements;
   &pgstattuple;
   &pgtrgm;
 + &postgres-fdw;
   &seg;
   &sepgsql;
   &contrib-spi;
           </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>
      <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>
         <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>
   --       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;
   
  \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;
          <!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">
            <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
      --- /dev/null
   +<!-- doc/src/sgml/postgres-fdw.sgml -->
 +
 +<sect1 id="postgres-fdw" xreflabel="postgres_fdw">
 + <title>postgres_fdw</title>
 +
 + <indexterm zone="postgres-fdw">
 +  <primary>postgres_fdw</primary>
 + </indexterm>
 +
 + <para>
 +  The <filename>postgres_fdw</> module provides the foreign-data wrapper
 +  <literal>postgres_fdw</literal>, which can be used to access data
 +  stored in external <productname>PostgreSQL</productname> servers.
 + </para>
 +
 + <para>
 +  The functionality provided by this module overlaps substantially
 +  with the functionality of the older <xref linkend="dblink"> module.
 +  But <filename>postgres_fdw</> provides more transparent and
 +  standards-compliant syntax for accessing remote tables, and can give
 +  better performance in many cases.
 + </para>
 +
 + <para>
 +  To prepare for remote access using <filename>postgres_fdw</>:
 +  <orderedlist spacing="compact">
 +   <listitem>
 +    <para>
 +     Install the  <filename>postgres_fdw</> extension using <xref
 +     linkend="sql-createextension">.
 +    </para>
 +   </listitem>
 +   <listitem>
 +    <para>
 +     Create a foreign server object, using <xref linkend="sql-createserver">,
 +     to represent each remote database you want to connect to.
 +     Specify connection information, except <literal>user</literal> and
 +     <literal>password</literal>, as options of the server object.
 +    </para>
 +   </listitem>
 +   <listitem>
 +    <para>
 +     Create a user mapping, using <xref linkend="sql-createusermapping">, for
 +     each database user you want to allow to access each foreign server.
 +     Specify the remote user name and password to use as
 +     <literal>user</literal> and <literal>password</literal> options of the
 +     user mapping.
 +    </para>
 +   </listitem>
 +   <listitem>
 +    <para>
 +     Create a foreign table, using <xref linkend="sql-createforeigntable">,
 +     for each remote table you want to access.  The columns of the foreign
 +     table must match the referenced remote table.  You can, however, use
 +     table and/or column names different from the remote table's, if you
 +     specify the correct remote names as options of the foreign table object.
 +    </para>
 +   </listitem>
 +  </orderedlist>
 + </para>
 +
 + <para>
 +  Now you need only <command>SELECT</> from a foreign table to access
 +  the data stored in its underlying remote table.
 + </para>
 +
 + <para>
 +  It is generally recommended that the columns of a foreign table be declared
 +  with exactly the same data types, and collations if applicable, as the
 +  referenced columns of the remote table.  Although <filename>postgres_fdw</>
 +  is currently rather forgiving about performing data type conversions at
 +  need, surprising semantic anomalies may arise when types or collations do
 +  not match, due to the remote server interpreting <literal>WHERE</> clauses
 +  slightly differently from the local server.
 + </para>
 +
 + <para>
 +  Note that a foreign table can be declared with fewer columns, or with a
 +  different column order, than its underlying remote table has.  Matching
 +  of columns to the remote table is by name, not position.
 + </para>
 +
 + <sect2>
 +  <title>FDW Options of postgres_fdw</title>
 +
 +  <sect3>
 +   <title>Connection Options</title>
 +
 +   <para>
 +    A foreign server using the <filename>postgres_fdw</> foreign data wrapper
 +    can have the same options that <application>libpq</> accepts in
 +    connection strings, as described in <xref linkend="libpq-paramkeywords">,
 +    except that these options are not allowed:
 +
 +    <itemizedlist spacing="compact">
 +     <listitem>
 +      <para>
 +       <literal>user</literal> and <literal>password</literal> (specify these
 +       for a user mapping, instead)
 +      </para>
 +     </listitem>
 +     <listitem>
 +      <para>
 +       <literal>client_encoding</> (this is automatically set from the local
 +       server encoding)
 +      </para>
 +     </listitem>
 +     <listitem>
 +      <para>
 +       <literal>fallback_application_name</> (always set to
 +       <literal>postgres_fdw</>)
 +      </para>
 +     </listitem>
 +    </itemizedlist>
 +   </para>
 +
 +   <para>
 +    Only superusers may connect to foreign servers without password
 +    authentication, so always specify the <literal>password</literal> option
 +    for user mappings belonging to non-superusers.
 +   </para>
 +  </sect3>
 +
 +  <sect3>
 +   <title>Object Name Options</title>
 +
 +   <para>
 +    These options can be used to control the names used in SQL statements
 +    sent to the remote <productname>PostgreSQL</productname> server.  These
 +    options are needed when a foreign table is created with names different
 +    from the underlying remote table's names.
 +   </para>
 +
 +   <variablelist>
 +
 +    <varlistentry>
 +     <term><literal>schema_name</literal></term>
 +     <listitem>
 +      <para>
 +       This option, which can be specified for a foreign table, gives the
 +       schema name to use for the foreign table on the remote server.  If this
 +       option is omitted, the name of the foreign table's schema is used.
 +      </para>
 +     </listitem>
 +    </varlistentry>
 +
 +    <varlistentry>
 +     <term><literal>table_name</literal></term>
 +     <listitem>
 +      <para>
 +       This option, which can be specified for a foreign table, gives the
 +       table name to use for the foreign table on the remote server.  If this
 +       option is omitted, the foreign table's name is used.
 +      </para>
 +     </listitem>
 +    </varlistentry>
 +
 +    <varlistentry>
 +     <term><literal>column_name</literal></term>
 +     <listitem>
 +      <para>
 +       This option, which can be specified for a column of a foreign table,
 +       gives the column name to use for the column on the remote server.
 +       If this option is omitted, the column's name is used.
 +      </para>
 +     </listitem>
 +    </varlistentry>
 +
 +   </variablelist>
 +
 +  </sect3>
 +
 +  <sect3>
 +   <title>Cost Estimation Options</title>
 +
 +   <para>
 +    <filename>postgres_fdw</> retrieves remote data by executing queries
 +    against remote servers, so ideally the estimated cost of scanning a
 +    foreign table should be whatever it costs to be done on the remote
 +    server, plus some overhead for communication.  The most reliable way to
 +    get such an estimate is to ask the remote server and then add something
 +    for overhead — but for simple queries, it may not be worth the cost
 +    of an additional remote query to get a cost estimate.
 +    So <filename>postgres_fdw</> provides the following options to control
 +    how cost estimation is done:
 +   </para>
 +
 +   <variablelist>
 +
 +    <varlistentry>
 +     <term><literal>use_remote_estimate</literal></term>
 +     <listitem>
 +      <para>
 +       This option, which can be specified for a foreign table or a foreign
 +       server, controls whether <filename>postgres_fdw</> issues remote
 +       <command>EXPLAIN</command> commands to obtain cost estimates.
 +       A setting for a foreign table overrides any setting for its server,
 +       but only for that table.
 +       The default is <literal>false</literal>.
 +      </para>
 +     </listitem>
 +    </varlistentry>
 +
 +    <varlistentry>
 +     <term><literal>fdw_startup_cost</literal></term>
 +     <listitem>
 +      <para>
 +       This option, which can be specified for a foreign server, is a numeric
 +       value that is added to the estimated startup cost of any foreign-table
 +       scan on that server.  This represents the additional overhead of
 +       establishing a connection, parsing and planning the query on the
 +       remote side, etc.
 +       The default value is <literal>100</literal>.
 +      </para>
 +     </listitem>
 +    </varlistentry>
 +
 +    <varlistentry>
 +     <term><literal>fdw_tuple_cost</literal></term>
 +     <listitem>
 +      <para>
 +       This option, which can be specified for a foreign server, is a numeric
 +       value that is used as extra cost per-tuple for foreign-table
 +       scans on that server.  This represents the additional overhead of
 +       data transfer between servers.  You might increase or decrease this
 +       number to reflect higher or lower network delay to the remote server.
 +       The default value is <literal>0.01</literal>.
 +      </para>
 +     </listitem>
 +    </varlistentry>
 +
 +   </variablelist>
 +
 +   <para>
 +    When <literal>use_remote_estimate</literal> is true,
 +    <filename>postgres_fdw</> obtains rowcount and cost estimates from the
 +    remote server and then adds <literal>fdw_startup_cost</literal> and
 +    <literal>fdw_tuple_cost</literal> to the cost estimates.  When
 +    <literal>use_remote_estimate</literal> is false,
 +    <filename>postgres_fdw</> performs local rowcount and cost estimation
 +    and then adds <literal>fdw_startup_cost</literal> and
 +    <literal>fdw_tuple_cost</literal> to the cost estimates.  This local
 +    estimation is unlikely to be very accurate unless local copies of the
 +    remote table's statistics are available.  Running
 +    <xref linkend="sql-analyze"> on the foreign table is the way to update
 +    the local statistics; this will perform a scan of the remote table and
 +    then calculate and store statistics just as though the table were local.
 +    Keeping local statistics can be a useful way to reduce per-query planning
 +    overhead for a remote table — but if the remote table is
 +    frequently updated, the local statistics will soon be obsolete.
 +   </para>
 +
 +  </sect3>
 + </sect2>
 +
 + <sect2>
 +  <title>Connection Management</title>
 +
 +  <para>
 +   <filename>postgres_fdw</filename> establishes a connection to a
 +   foreign server during the first query that uses a foreign table
 +   associated with the foreign server.  This connection is kept and
 +   re-used for subsequent queries in the same session.  However, if
 +   multiple user identities (user mappings) are used to access the foreign
 +   server, a connection is established for each user mapping.
 +  </para>
 + </sect2>
 +
 + <sect2>
 +  <title>Transaction Management</title>
 +
 +  <para>
 +   During a query that references any remote tables on a foreign server,
 +   <filename>postgres_fdw</filename> opens a transaction on the
 +   remote server if one is not already open corresponding to the current
 +   local transaction.  The remote transaction is committed or aborted when
 +   the local transaction commits or aborts.  Savepoints are similarly
 +   managed by creating corresponding remote savepoints.
 +  </para>
 +
 +  <para>
 +   The remote transaction uses <literal>SERIALIZABLE</>
 +   isolation level when the local transaction has <literal>SERIALIZABLE</>
 +   isolation level; otherwise it uses <literal>REPEATABLE READ</>
 +   isolation level.  This choice ensures that if a query performs multiple
 +   table scans on the remote server, it will get snapshot-consistent results
 +   for all the scans.  A consequence is that successive queries within a
 +   single transaction will see the same data from the remote server, even if
 +   concurrent updates are occurring on the remote server due to other
 +   activities.  That behavior would be expected anyway if the local
 +   transaction uses <literal>SERIALIZABLE</> or <literal>REPEATABLE READ</>
 +   isolation level, but it might be surprising for a <literal>READ
 +   COMMITTED</> local transaction.  A future
 +   <productname>PostgreSQL</productname> release might modify these rules.
 +  </para>
 + </sect2>
 +
 + <sect2>
 +  <title>Remote Query Optimization</title>
 +
 +  <para>
 +   <filename>postgres_fdw</> attempts to optimize remote queries to reduce
 +   the amount of data transferred from foreign servers.  This is done by
 +   sending query <literal>WHERE</> clauses to the remote server for
 +   execution, and by not retrieving table columns that are not needed for
 +   the current query.  To reduce the risk of misexecution of queries,
 +   <literal>WHERE</> clauses are not sent to the remote server unless they use
 +   only built-in data types, operators, and functions.  Operators and
 +   functions in the clauses must be <literal>IMMUTABLE</> as well.
 +  </para>
 +
 +  <para>
 +   The query that is actually sent to the remote server for execution can
 +   be examined using <command>EXPLAIN VERBOSE</>.
 +  </para>
 + </sect2>
 +
 + <sect2>
 +  <title>Author</title>
 +  <para>
 +   Shigeru Hanada <email>shigeru.hanada@gmail.com</email>
 +  </para>
 + </sect2>
 +
 +</sect1>
              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
                   <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
             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>
            <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>
     </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>
  
       <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>
     <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 (
            <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>
     <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>
      <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>
                 to create new servers using that foreign-data wrapper.
        </para>
        <para>
 -       For servers, this privilege enables the grantee to create,
 -       alter, and drop his own user's user mappings associated with
 -       that server.  Also, it enables the grantee to query the options
 -       of the server and associated user mappings.
 +       For servers, this privilege enables the grantee to create foreign
 +       tables using the server, and also to create, alter, or drop his own
 +       user's user mappings associated with that server.
        </para>
       </listitem>
      </varlistentry>
                 with a valid <acronym>URI</acronym> prefix
         (<literal>postgresql://</literal>
         or <literal>postgres://</literal>), it is treated as a
 -       <parameter>conninfo</parameter> string. See <xref linkend="libpq-connect"> for more information.
 +       <parameter>conninfo</parameter> string. See <xref
 +       linkend="libpq-connstring"> for more information.
        </para>
        </listitem>
      </varlistentry>
                 with a valid <acronym>URI</acronym> prefix
         (<literal>postgresql://</literal>
         or <literal>postgres://</literal>), it is treated as a
 -       <parameter>conninfo</parameter> string. See <xref linkend="libpq-connect"> for more information.
 +       <parameter>conninfo</parameter> string. See <xref
 +       linkend="libpq-connstring"> for more information.
        </para>
        </listitem>
      </varlistentry>
   $ <userinput>psql "service=myservice sslmode=require"</userinput>
  $ <userinput>psql postgresql://dbmaster:5433/mydb?sslmode=require</userinput>
  </programlisting>
 -     This way you can also use <acronym>LDAP</acronym> for connection parameter lookup as
 -     described in <xref linkend="libpq-ldap">.
 -     See <xref linkend="libpq-connect"> for more information on all the
 +     This way you can also use <acronym>LDAP</acronym> for connection
 +     parameter lookup as described in <xref linkend="libpq-ldap">.
 +     See <xref linkend="libpq-paramkeywords"> for more information on all the
       available connection options.
      </para>
  
             (<xref linkend="ssl-tcp">). The TCP client must connect using
     <literal>sslmode=verify-ca</> or
     <literal>verify-full</> and have the appropriate root certificate
 -   file installed (<xref linkend="libpq-connect">).
 +   file installed (<xref linkend="libq-ssl-certificates">).
    </para>
   </sect1>
  
          
  /*
   * Validate the generic option given to SERVER or USER MAPPING.
 - * Raise an ERROR if the option or its value is considered
 - * invalid.
 + * Raise an ERROR if the option or its value is considered invalid.
   *
   * Valid server options are all libpq conninfo options except
   * user and password -- these may only appear in USER MAPPING options.
 + *
 + * Caution: this function is deprecated, and is now meant only for testing
 + * purposes, because the list of options it knows about doesn't necessarily
 + * square with those known to whichever libpq instance you might be using.
 + * Inquire of libpq itself, instead.
   */
  Datum
  postgresql_fdw_validator(PG_FUNCTION_ARGS)