diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index d2b98e10f3a5e..354331247a822 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -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)
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 697de60dfe51b..e45ae970609a1 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -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.
@@ -217,11 +216,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,
@@ -229,6 +229,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;
@@ -237,6 +238,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;
@@ -374,11 +378,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;
/*
@@ -422,11 +426,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;
/*
@@ -464,9 +468,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;
/*
@@ -613,10 +617,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;
/*
@@ -669,27 +673,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.
- *
- * 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.
+ * 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.
*
- * 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);
}
@@ -1355,8 +1355,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;
}
@@ -1429,8 +1429,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));
}
/*
@@ -1555,7 +1555,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;
}
@@ -1750,8 +1750,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));
}
/*
@@ -1831,7 +1831,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));
}
/*
@@ -1847,7 +1847,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);
}
@@ -1873,7 +1873,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);
}
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 7547ec28172e0..380ac80ab3a3c 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -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;
+}
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 775eaa07da89c..032e37ac40375 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -47,40 +47,6 @@ PG_MODULE_MAGIC;
/* 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
-{
- /* 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.
*
@@ -406,6 +372,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)
{
@@ -417,6 +384,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)
{
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 3835ddb79ac61..15381f8964b04 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -20,6 +20,41 @@
#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,
@@ -75,4 +112,8 @@ extern void deparseAnalyzeSql(StringInfo buf, Relation rel,
List **retrieved_attrs);
extern void deparseStringLiteral(StringInfo buf, const char *val);
+/* 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
index 0000000000000..ebb1f5e31770c
--- /dev/null
+++ b/contrib/postgres_fdw/shippable.c
@@ -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;
+}
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 7c922821e988f..5a829d537a698 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -264,6 +264,46 @@
+
+ Remote Execution Options
+
+
+ By default, only 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 WHERE> clauses for remote
+ execution. This behavior can be controlled using the following option:
+
+
+
+
+
+ extensions
+
+
+ This option is a comma-separated list of names
+ of 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.
+
+
+
+
+
+
+
+ When using the extensions option, 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.
+
+
+
+
Updatability Options
@@ -427,8 +467,10 @@
execution, and by not retrieving table columns that are not needed for
the current query. To reduce the risk of misexecution of queries,
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 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 extensions>
+ option. Operators and functions in such clauses must
+ be IMMUTABLE> as well.
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index a8519835cc662..e046f05f28c65 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -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,
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 95f2a848d39d3..f6a3db1eb7782 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -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);