]> granicus.if.org Git - postgresql/commitdiff
Allow postgres_fdw to ship extension funcs/operators for remote execution.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 3 Nov 2015 23:42:02 +0000 (18:42 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 3 Nov 2015 23:42:18 +0000 (18:42 -0500)
The user can whitelist specified extension(s) in the foreign server's
options, whereupon we will treat immutable functions and operators of those
extensions as candidates to be sent for remote execution.

Whitelisting an extension in this way basically promises that the extension
exists on the remote server and behaves compatibly with the local instance.
We have no way to prove that formally, so we have to rely on the user to
get it right.  But this seems like something that people can usually get
right in practice.

We might in future allow functions and operators to be whitelisted
individually, but extension granularity is a very convenient special case,
so it got done first.

The patch as-committed lacks any regression tests, which is unfortunate,
but introducing dependencies on other extensions for testing purposes
would break "make installcheck" scenarios, which is worse.  I have some
ideas about klugy ways around that, but it seems like material for a
separate patch.  For the moment, leave the problem open.

Paul Ramsey, hacked up a bit more by me

contrib/postgres_fdw/Makefile
contrib/postgres_fdw/deparse.c
contrib/postgres_fdw/option.c
contrib/postgres_fdw/postgres_fdw.c
contrib/postgres_fdw/postgres_fdw.h
contrib/postgres_fdw/shippable.c [new file with mode: 0644]
doc/src/sgml/postgres-fdw.sgml
src/backend/utils/adt/format_type.c
src/include/utils/builtins.h

index d2b98e10f3a5e455886e6c605ae6e0bad5930799..354331247a82247ff7714b20c0ab87b8869850ec 100644 (file)
@@ -1,7 +1,7 @@
 # contrib/postgres_fdw/Makefile
 
 MODULE_big = postgres_fdw
-OBJS = postgres_fdw.o option.o deparse.o connection.o $(WIN32RES)
+OBJS = postgres_fdw.o option.o deparse.o connection.o shippable.o $(WIN32RES)
 PGFILEDESC = "postgres_fdw - foreign data wrapper for PostgreSQL"
 
 PG_CPPFLAGS = -I$(libpq_srcdir)
index 3cb728fa693f6b2661bd3e46c2527893785c12a7..c8232f2c16f36866b015990d03542203233964ba 100644 (file)
@@ -38,7 +38,6 @@
 #include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/sysattr.h"
-#include "access/transam.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_operator.h"
@@ -102,7 +101,7 @@ typedef struct deparse_expr_cxt
 static bool foreign_expr_walker(Node *node,
                                        foreign_glob_cxt *glob_cxt,
                                        foreign_loc_cxt *outer_cxt);
-static bool is_builtin(Oid procid);
+static char *deparse_type_name(Oid type_oid, int32 typemod);
 
 /*
  * Functions to construct string representation of a node tree.
@@ -220,11 +219,12 @@ is_foreign_expr(PlannerInfo *root,
  * In addition, *outer_cxt is updated with collation information.
  *
  * We must check that the expression contains only node types we can deparse,
- * that all types/functions/operators are safe to send (which we approximate
- * as being built-in), and that all collations used in the expression derive
- * from Vars of the foreign table.  Because of the latter, the logic is
- * pretty close to assign_collations_walker() in parse_collate.c, though we
- * can assume here that the given expression is valid.
+ * that all types/functions/operators are safe to send (they are "shippable"),
+ * and that all collations used in the expression derive from Vars of the
+ * foreign table.  Because of the latter, the logic is pretty close to
+ * assign_collations_walker() in parse_collate.c, though we can assume here
+ * that the given expression is valid.  Note function mutability is not
+ * currently considered here.
  */
 static bool
 foreign_expr_walker(Node *node,
@@ -232,6 +232,7 @@ foreign_expr_walker(Node *node,
                                        foreign_loc_cxt *outer_cxt)
 {
        bool            check_type = true;
+       PgFdwRelationInfo *fpinfo;
        foreign_loc_cxt inner_cxt;
        Oid                     collation;
        FDWCollateState state;
@@ -240,6 +241,9 @@ foreign_expr_walker(Node *node,
        if (node == NULL)
                return true;
 
+       /* May need server info from baserel's fdw_private struct */
+       fpinfo = (PgFdwRelationInfo *) (glob_cxt->foreignrel->fdw_private);
+
        /* Set up inner_cxt for possible recursion to child nodes */
        inner_cxt.collation = InvalidOid;
        inner_cxt.state = FDW_COLLATE_NONE;
@@ -377,11 +381,11 @@ foreign_expr_walker(Node *node,
                                FuncExpr   *fe = (FuncExpr *) node;
 
                                /*
-                                * If function used by the expression is not built-in, it
+                                * If function used by the expression is not shippable, it
                                 * can't be sent to remote because it might have incompatible
                                 * semantics on remote side.
                                 */
-                               if (!is_builtin(fe->funcid))
+                               if (!is_shippable(fe->funcid, ProcedureRelationId, fpinfo))
                                        return false;
 
                                /*
@@ -425,11 +429,11 @@ foreign_expr_walker(Node *node,
                                OpExpr     *oe = (OpExpr *) node;
 
                                /*
-                                * Similarly, only built-in operators can be sent to remote.
-                                * (If the operator is, surely its underlying function is
-                                * too.)
+                                * Similarly, only shippable operators can be sent to remote.
+                                * (If the operator is shippable, we assume its underlying
+                                * function is too.)
                                 */
-                               if (!is_builtin(oe->opno))
+                               if (!is_shippable(oe->opno, OperatorRelationId, fpinfo))
                                        return false;
 
                                /*
@@ -467,9 +471,9 @@ foreign_expr_walker(Node *node,
                                ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node;
 
                                /*
-                                * Again, only built-in operators can be sent to remote.
+                                * Again, only shippable operators can be sent to remote.
                                 */
-                               if (!is_builtin(oe->opno))
+                               if (!is_shippable(oe->opno, OperatorRelationId, fpinfo))
                                        return false;
 
                                /*
@@ -616,10 +620,10 @@ foreign_expr_walker(Node *node,
        }
 
        /*
-        * 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 result type of given expression is not shippable, it can't be sent
+        * to remote because it might have incompatible semantics on remote side.
         */
-       if (check_type && !is_builtin(exprType(node)))
+       if (check_type && !is_shippable(exprType(node), TypeRelationId, fpinfo))
                return false;
 
        /*
@@ -672,27 +676,23 @@ foreign_expr_walker(Node *node,
 }
 
 /*
- * Return true if given object is one of PostgreSQL's built-in objects.
- *
- * We use FirstBootstrapObjectId as the cutoff, so that we only consider
- * objects with hand-assigned OIDs to be "built in", not for instance any
- * function or type defined in the information_schema.
+ * Convert type OID + typmod info into a type name we can ship to the remote
+ * server.  Someplace else had better have verified that this type name is
+ * expected to be known on the remote end.
  *
- * Our constraints for dealing with types are tighter than they are for
- * functions or operators: we want to accept only types that are in pg_catalog,
- * else format_type might incorrectly fail to schema-qualify their names.
- * (This could be fixed with some changes to format_type, but for now there's
- * no need.)  Thus we must exclude information_schema types.
- *
- * 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.
+ * This is almost just format_type_with_typemod(), except that if left to its
+ * own devices, that function will make schema-qualification decisions based
+ * on the local search_path, which is wrong.  We must schema-qualify all
+ * type names that are not in pg_catalog.  We assume here that built-in types
+ * are all in pg_catalog and need not be qualified; otherwise, qualify.
  */
-static bool
-is_builtin(Oid oid)
+static char *
+deparse_type_name(Oid type_oid, int32 typemod)
 {
-       return (oid < FirstBootstrapObjectId);
+       if (is_builtin(type_oid))
+               return format_type_with_typemod(type_oid, typemod);
+       else
+               return format_type_with_typemod_qualified(type_oid, typemod);
 }
 
 
@@ -1358,8 +1358,8 @@ deparseConst(Const *node, deparse_expr_cxt *context)
        {
                appendStringInfoString(buf, "NULL");
                appendStringInfo(buf, "::%s",
-                                                format_type_with_typemod(node->consttype,
-                                                                                                 node->consttypmod));
+                                                deparse_type_name(node->consttype,
+                                                                                  node->consttypmod));
                return;
        }
 
@@ -1432,8 +1432,8 @@ deparseConst(Const *node, deparse_expr_cxt *context)
        }
        if (needlabel)
                appendStringInfo(buf, "::%s",
-                                                format_type_with_typemod(node->consttype,
-                                                                                                 node->consttypmod));
+                                                deparse_type_name(node->consttype,
+                                                                                  node->consttypmod));
 }
 
 /*
@@ -1558,7 +1558,7 @@ deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context)
 
                deparseExpr((Expr *) linitial(node->args), context);
                appendStringInfo(buf, "::%s",
-                                                format_type_with_typemod(rettype, coercedTypmod));
+                                                deparse_type_name(rettype, coercedTypmod));
                return;
        }
 
@@ -1753,8 +1753,8 @@ deparseRelabelType(RelabelType *node, deparse_expr_cxt *context)
        deparseExpr(node->arg, context);
        if (node->relabelformat != COERCE_IMPLICIT_CAST)
                appendStringInfo(context->buf, "::%s",
-                                                format_type_with_typemod(node->resulttype,
-                                                                                                 node->resulttypmod));
+                                                deparse_type_name(node->resulttype,
+                                                                                  node->resulttypmod));
 }
 
 /*
@@ -1834,7 +1834,7 @@ deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context)
        /* 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));
+                                                deparse_type_name(node->array_typeid, -1));
 }
 
 /*
@@ -1850,7 +1850,7 @@ printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod,
                                 deparse_expr_cxt *context)
 {
        StringInfo      buf = context->buf;
-       char       *ptypename = format_type_with_typemod(paramtype, paramtypmod);
+       char       *ptypename = deparse_type_name(paramtype, paramtypmod);
 
        appendStringInfo(buf, "$%d::%s", paramindex, ptypename);
 }
@@ -1876,7 +1876,7 @@ printRemotePlaceholder(Oid paramtype, int32 paramtypmod,
                                           deparse_expr_cxt *context)
 {
        StringInfo      buf = context->buf;
-       char       *ptypename = format_type_with_typemod(paramtype, paramtypmod);
+       char       *ptypename = deparse_type_name(paramtype, paramtypmod);
 
        appendStringInfo(buf, "((SELECT null::%s)::%s)", ptypename, ptypename);
 }
@@ -1890,10 +1890,10 @@ void
 appendOrderByClause(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel,
                                        List *pathkeys)
 {
-       ListCell                        *lcell;
-       deparse_expr_cxt        context;
-       int                                     nestlevel;
-       char                            *delim = " ";
+       ListCell   *lcell;
+       deparse_expr_cxt context;
+       int                     nestlevel;
+       char       *delim = " ";
 
        /* Set up context struct for recursion */
        context.root = root;
@@ -1907,8 +1907,8 @@ appendOrderByClause(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel,
        appendStringInfo(buf, " ORDER BY");
        foreach(lcell, pathkeys)
        {
-               PathKey                         *pathkey = lfirst(lcell);
-               Expr                            *em_expr;
+               PathKey    *pathkey = lfirst(lcell);
+               Expr       *em_expr;
 
                em_expr = find_em_expr_for_rel(pathkey->pk_eclass, baserel);
                Assert(em_expr != NULL);
index 7547ec28172e057f317a8cc782a6c795f09ad091..380ac80ab3a3c6b6323079ce05cd3279e5819137 100644 (file)
@@ -19,6 +19,8 @@
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_user_mapping.h"
 #include "commands/defrem.h"
+#include "commands/extension.h"
+#include "utils/builtins.h"
 
 
 /*
@@ -124,6 +126,11 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
                                                 errmsg("%s requires a non-negative numeric value",
                                                                def->defname)));
                }
+               else if (strcmp(def->defname, "extensions") == 0)
+               {
+                       /* check list syntax, warn about uninstalled extensions */
+                       (void) ExtractExtensionList(defGetString(def), true);
+               }
        }
 
        PG_RETURN_VOID();
@@ -150,6 +157,8 @@ InitPgFdwOptions(void)
                /* cost factors */
                {"fdw_startup_cost", ForeignServerRelationId, false},
                {"fdw_tuple_cost", ForeignServerRelationId, false},
+               /* shippable extensions */
+               {"extensions", ForeignServerRelationId, false},
                /* updatable is available on both server and table */
                {"updatable", ForeignServerRelationId, false},
                {"updatable", ForeignTableRelationId, false},
@@ -293,3 +302,48 @@ ExtractConnectionOptions(List *defelems, const char **keywords,
        }
        return i;
 }
+
+/*
+ * Parse a comma-separated string and return a List of the OIDs of the
+ * extensions named in the string.  If any names in the list cannot be
+ * found, report a warning if warnOnMissing is true, else just silently
+ * ignore them.
+ */
+List *
+ExtractExtensionList(const char *extensionsString, bool warnOnMissing)
+{
+       List       *extensionOids = NIL;
+       List       *extlist;
+       ListCell   *lc;
+
+       /* SplitIdentifierString scribbles on its input, so pstrdup first */
+       if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist))
+       {
+               /* syntax error in name list */
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("parameter \"%s\" must be a list of extension names",
+                                               "extensions")));
+       }
+
+       foreach(lc, extlist)
+       {
+               const char *extension_name = (const char *) lfirst(lc);
+               Oid                     extension_oid = get_extension_oid(extension_name, true);
+
+               if (OidIsValid(extension_oid))
+               {
+                       extensionOids = lappend_oid(extensionOids, extension_oid);
+               }
+               else if (warnOnMissing)
+               {
+                       ereport(WARNING,
+                                       (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                        errmsg("extension \"%s\" is not installed",
+                                                       extension_name)));
+               }
+       }
+
+       list_free(extlist);
+       return extensionOids;
+}
index 914e6704c26de4b715aae5b3adf4be516a1aad3d..cd4ed0c94dbc8e9ce3b2d1c3ea9f7c00b7fb1ce9 100644 (file)
@@ -50,40 +50,6 @@ PG_MODULE_MAGIC;
 /* If no remote estimates, assume a sort costs 20% extra */
 #define DEFAULT_FDW_SORT_MULTIPLIER 1.2
 
-/*
- * FDW-specific planner information kept in RelOptInfo.fdw_private for a
- * foreign table.  This information is collected by postgresGetForeignRelSize.
- */
-typedef struct PgFdwRelationInfo
-{
-       /* baserestrictinfo clauses, broken down into safe and unsafe subsets. */
-       List       *remote_conds;
-       List       *local_conds;
-
-       /* Bitmap of attr numbers we need to fetch from the remote server. */
-       Bitmapset  *attrs_used;
-
-       /* Cost and selectivity of local_conds. */
-       QualCost        local_conds_cost;
-       Selectivity local_conds_sel;
-
-       /* Estimated size and cost for a scan with baserestrictinfo quals. */
-       double          rows;
-       int                     width;
-       Cost            startup_cost;
-       Cost            total_cost;
-
-       /* Options extracted from catalogs. */
-       bool            use_remote_estimate;
-       Cost            fdw_startup_cost;
-       Cost            fdw_tuple_cost;
-
-       /* Cached catalog information. */
-       ForeignTable *table;
-       ForeignServer *server;
-       UserMapping *user;                      /* only set in use_remote_estimate mode */
-} PgFdwRelationInfo;
-
 /*
  * Indexes of FDW-private information stored in fdw_private lists.
  *
@@ -409,6 +375,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
        fpinfo->use_remote_estimate = false;
        fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST;
        fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST;
+       fpinfo->shippable_extensions = NIL;
 
        foreach(lc, fpinfo->server->options)
        {
@@ -420,6 +387,9 @@ postgresGetForeignRelSize(PlannerInfo *root,
                        fpinfo->fdw_startup_cost = strtod(defGetString(def), NULL);
                else if (strcmp(def->defname, "fdw_tuple_cost") == 0)
                        fpinfo->fdw_tuple_cost = strtod(defGetString(def), NULL);
+               else if (strcmp(def->defname, "extensions") == 0)
+                       fpinfo->shippable_extensions =
+                               ExtractExtensionList(defGetString(def), false);
        }
        foreach(lc, fpinfo->table->options)
        {
index 8956cd2cf1c404020cc63b28a1534ce86ea3dab1..f243de8d6237822558422d76219c697490600547 100644 (file)
 
 #include "libpq-fe.h"
 
+/*
+ * FDW-specific planner information kept in RelOptInfo.fdw_private for a
+ * foreign table.  This information is collected by postgresGetForeignRelSize.
+ */
+typedef struct PgFdwRelationInfo
+{
+       /* baserestrictinfo clauses, broken down into safe and unsafe subsets. */
+       List       *remote_conds;
+       List       *local_conds;
+
+       /* Bitmap of attr numbers we need to fetch from the remote server. */
+       Bitmapset  *attrs_used;
+
+       /* Cost and selectivity of local_conds. */
+       QualCost        local_conds_cost;
+       Selectivity local_conds_sel;
+
+       /* Estimated size and cost for a scan with baserestrictinfo quals. */
+       double          rows;
+       int                     width;
+       Cost            startup_cost;
+       Cost            total_cost;
+
+       /* Options extracted from catalogs. */
+       bool            use_remote_estimate;
+       Cost            fdw_startup_cost;
+       Cost            fdw_tuple_cost;
+       List       *shippable_extensions;       /* OIDs of whitelisted extensions */
+
+       /* Cached catalog information. */
+       ForeignTable *table;
+       ForeignServer *server;
+       UserMapping *user;                      /* only set in use_remote_estimate mode */
+} PgFdwRelationInfo;
+
 /* in postgres_fdw.c */
 extern int     set_transmission_modes(void);
 extern void reset_transmission_modes(int nestlevel);
@@ -37,6 +72,8 @@ extern void pgfdw_report_error(int elevel, PGresult *res, PGconn *conn,
 extern int ExtractConnectionOptions(List *defelems,
                                                 const char **keywords,
                                                 const char **values);
+extern List *ExtractExtensionList(const char *extensionsString,
+                                        bool warnOnMissing);
 
 /* in deparse.c */
 extern void classifyConditions(PlannerInfo *root,
@@ -76,6 +113,10 @@ extern void deparseAnalyzeSql(StringInfo buf, Relation rel,
 extern void deparseStringLiteral(StringInfo buf, const char *val);
 extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel);
 extern void appendOrderByClause(StringInfo buf, PlannerInfo *root,
-                                                               RelOptInfo *baserel, List *pathkeys);
+                                       RelOptInfo *baserel, List *pathkeys);
+
+/* in shippable.c */
+extern bool is_builtin(Oid objectId);
+extern bool is_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo);
 
 #endif   /* POSTGRES_FDW_H */
diff --git a/contrib/postgres_fdw/shippable.c b/contrib/postgres_fdw/shippable.c
new file mode 100644 (file)
index 0000000..ebb1f5e
--- /dev/null
@@ -0,0 +1,214 @@
+/*-------------------------------------------------------------------------
+ *
+ * shippable.c
+ *       Determine which database objects are shippable to a remote server.
+ *
+ * We need to determine whether particular functions, operators, and indeed
+ * data types are shippable to a remote server for execution --- that is,
+ * do they exist and have the same behavior remotely as they do locally?
+ * Built-in objects are generally considered shippable.  Other objects can
+ * be shipped if they are white-listed by the user.
+ *
+ * Note: there are additional filter rules that prevent shipping mutable
+ * functions or functions using nonportable collations.  Those considerations
+ * need not be accounted for here.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *       contrib/postgres_fdw/shippable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "postgres_fdw.h"
+
+#include "access/transam.h"
+#include "catalog/dependency.h"
+#include "utils/hsearch.h"
+#include "utils/inval.h"
+#include "utils/syscache.h"
+
+
+/* Hash table for caching the results of shippability lookups */
+static HTAB *ShippableCacheHash = NULL;
+
+/*
+ * Hash key for shippability lookups.  We include the FDW server OID because
+ * decisions may differ per-server.  Otherwise, objects are identified by
+ * their (local!) OID and catalog OID.
+ */
+typedef struct
+{
+       /* XXX we assume this struct contains no padding bytes */
+       Oid                     objid;                  /* function/operator/type OID */
+       Oid                     classid;                /* OID of its catalog (pg_proc, etc) */
+       Oid                     serverid;               /* FDW server we are concerned with */
+} ShippableCacheKey;
+
+typedef struct
+{
+       ShippableCacheKey key;          /* hash key - must be first */
+       bool            shippable;
+} ShippableCacheEntry;
+
+
+/*
+ * Flush cache entries when pg_foreign_server is updated.
+ *
+ * We do this because of the possibility of ALTER SERVER being used to change
+ * a server's extensions option.  We do not currently bother to check whether
+ * objects' extension membership changes once a shippability decision has been
+ * made for them, however.
+ */
+static void
+InvalidateShippableCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+       HASH_SEQ_STATUS status;
+       ShippableCacheEntry *entry;
+
+       /*
+        * In principle we could flush only cache entries relating to the
+        * pg_foreign_server entry being outdated; but that would be more
+        * complicated, and it's probably not worth the trouble.  So for now, just
+        * flush all entries.
+        */
+       hash_seq_init(&status, ShippableCacheHash);
+       while ((entry = (ShippableCacheEntry *) hash_seq_search(&status)) != NULL)
+       {
+               if (hash_search(ShippableCacheHash,
+                                               (void *) &entry->key,
+                                               HASH_REMOVE,
+                                               NULL) == NULL)
+                       elog(ERROR, "hash table corrupted");
+       }
+}
+
+/*
+ * Initialize the backend-lifespan cache of shippability decisions.
+ */
+static void
+InitializeShippableCache(void)
+{
+       HASHCTL         ctl;
+
+       /* Create the hash table. */
+       MemSet(&ctl, 0, sizeof(ctl));
+       ctl.keysize = sizeof(ShippableCacheKey);
+       ctl.entrysize = sizeof(ShippableCacheEntry);
+       ShippableCacheHash =
+               hash_create("Shippability cache", 256, &ctl, HASH_ELEM | HASH_BLOBS);
+
+       /* Set up invalidation callback on pg_foreign_server. */
+       CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
+                                                                 InvalidateShippableCacheCallback,
+                                                                 (Datum) 0);
+}
+
+/*
+ * Returns true if given object (operator/function/type) is shippable
+ * according to the server options.
+ *
+ * Right now "shippability" is exclusively a function of whether the object
+ * belongs to an extension declared by the user.  In the future we could
+ * additionally have a whitelist of functions/operators declared one at a time.
+ */
+static bool
+lookup_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo)
+{
+       Oid                     extensionOid;
+
+       /*
+        * Is object a member of some extension?  (Note: this is a fairly
+        * expensive lookup, which is why we try to cache the results.)
+        */
+       extensionOid = getExtensionOfObject(classId, objectId);
+
+       /* If so, is that extension in fpinfo->shippable_extensions? */
+       if (OidIsValid(extensionOid) &&
+               list_member_oid(fpinfo->shippable_extensions, extensionOid))
+               return true;
+
+       return false;
+}
+
+/*
+ * Return true if given object is one of PostgreSQL's built-in objects.
+ *
+ * We use FirstBootstrapObjectId as the cutoff, so that we only consider
+ * objects with hand-assigned OIDs to be "built in", not for instance any
+ * function or type defined in the information_schema.
+ *
+ * Our constraints for dealing with types are tighter than they are for
+ * functions or operators: we want to accept only types that are in pg_catalog,
+ * else deparse_type_name might incorrectly fail to schema-qualify their names.
+ * Thus we must exclude information_schema types.
+ *
+ * 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.
+ */
+bool
+is_builtin(Oid objectId)
+{
+       return (objectId < FirstBootstrapObjectId);
+}
+
+/*
+ * is_shippable
+ *        Is this object (function/operator/type) shippable to foreign server?
+ */
+bool
+is_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo)
+{
+       ShippableCacheKey key;
+       ShippableCacheEntry *entry;
+
+       /* Built-in objects are presumed shippable. */
+       if (is_builtin(objectId))
+               return true;
+
+       /* Otherwise, give up if user hasn't specified any shippable extensions. */
+       if (fpinfo->shippable_extensions == NIL)
+               return false;
+
+       /* Initialize cache if first time through. */
+       if (!ShippableCacheHash)
+               InitializeShippableCache();
+
+       /* Set up cache hash key */
+       key.objid = objectId;
+       key.classid = classId;
+       key.serverid = fpinfo->server->serverid;
+
+       /* See if we already cached the result. */
+       entry = (ShippableCacheEntry *)
+               hash_search(ShippableCacheHash,
+                                       (void *) &key,
+                                       HASH_FIND,
+                                       NULL);
+
+       if (!entry)
+       {
+               /* Not found in cache, so perform shippability lookup. */
+               bool            shippable = lookup_shippable(objectId, classId, fpinfo);
+
+               /*
+                * Don't create a new hash entry until *after* we have the shippable
+                * result in hand, as the underlying catalog lookups might trigger a
+                * cache invalidation.
+                */
+               entry = (ShippableCacheEntry *)
+                       hash_search(ShippableCacheHash,
+                                               (void *) &key,
+                                               HASH_ENTER,
+                                               NULL);
+
+               entry->shippable = shippable;
+       }
+
+       return entry->shippable;
+}
index 7c922821e988ffde8711e8323ff310c8166187d4..5a829d537a69816a31c645442866aa33303ee1d6 100644 (file)
 
   </sect3>
 
+  <sect3>
+   <title>Remote Execution Options</title>
+
+   <para>
+    By default, only <literal>WHERE</> clauses using built-in operators and
+    functions will be considered for execution on the remote server.  Clauses
+    involving non-built-in functions are checked locally after rows are
+    fetched.  If such functions are available on the remote server and can be
+    relied on to produce the same results as they do locally, performance can
+    be improved by sending such <literal>WHERE</> clauses for remote
+    execution.  This behavior can be controlled using the following option:
+   </para>
+
+   <variablelist>
+
+    <varlistentry>
+     <term><literal>extensions</literal></term>
+     <listitem>
+      <para>
+       This option is a comma-separated list of names
+       of <productname>PostgreSQL</> extensions that are installed, in
+       compatible versions, on both the local and remote servers.  Functions
+       and operators that are immutable and belong to a listed extension will
+       be considered shippable to the remote server.
+       This option can only be specified for foreign servers, not per-table.
+      </para>
+     </listitem>
+    </varlistentry>
+
+   </variablelist>
+
+   <para>
+    When using the <literal>extensions</literal> option, <emphasis>it is the
+    user's responsibility</> that the listed extensions exist and behave
+    identically on both the local and remote servers.  Otherwise, remote
+    queries may fail or behave unexpectedly.
+   </para>
+
+  </sect3>
+
   <sect3>
    <title>Updatability Options</title>
 
    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.
+   only data types, operators, and functions that are built-in or belong to an
+   extension that's listed in the foreign server's <literal>extensions</>
+   option.  Operators and functions in such clauses must
+   be <literal>IMMUTABLE</> as well.
   </para>
 
   <para>
index a8519835cc66215aebd193ead8f682438c1d0d12..e046f05f28c659c0c988e429f3100fdecedb31ff 100644 (file)
@@ -97,7 +97,8 @@ format_type_be(Oid type_oid)
 }
 
 /*
- * This version returns a name which is always qualified.
+ * This version returns a name that is always qualified (unless it's one
+ * of the SQL-keyword type names, such as TIMESTAMP WITH TIME ZONE).
  */
 char *
 format_type_be_qualified(Oid type_oid)
@@ -114,6 +115,19 @@ format_type_with_typemod(Oid type_oid, int32 typemod)
        return format_type_internal(type_oid, typemod, true, false, false);
 }
 
+/*
+ * This version allows a nondefault typemod to be specified, and forces
+ * qualification of normal type names.
+ */
+char *
+format_type_with_typemod_qualified(Oid type_oid, int32 typemod)
+{
+       return format_type_internal(type_oid, typemod, true, false, true);
+}
+
+/*
+ * Common workhorse.
+ */
 static char *
 format_type_internal(Oid type_oid, int32 typemod,
                                         bool typemod_given, bool allow_invalid,
index fc1679ed462bbe602010f412a952463524e983d2..c193e4425ed9864cf2db82fec1ed90f8939a79bc 100644 (file)
@@ -1105,6 +1105,7 @@ extern Datum format_type(PG_FUNCTION_ARGS);
 extern char *format_type_be(Oid type_oid);
 extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
+extern char *format_type_with_typemod_qualified(Oid type_oid, int32 typemod);
 extern Datum oidvectortypes(PG_FUNCTION_ARGS);
 extern int32 type_maximum_size(Oid type_oid, int32 typemod);